vendor: 6,850 bytes, lines 1-141
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-1.13.4/af-2.5.3/b-2.3.6/b-colvis-2.3.6/b-html5-2.3.6/b-print-2.3.6/cr-1.6.2/fc-4.2.2/fh-3.3.2/kt-2.9.0/r-2.4.1/rg-1.3.1/rr-1.3.3/sc-2.1.1/sb-1.4.2/sp-2.1.2/sl-1.6.2/sr-1.2.2 8 * 9 * Included libraries: 10 * DataTables 1.13.4, AutoFill 2.5.3, Buttons 2.3.6, Column visibility 2.3.6, HTML5 export 2.3.6, Print view 2.3.6, ColReorder 1.6.2, FixedColumns 4.2.2, FixedHeader 3.3.2, KeyTable 2.9.0, Responsive 2.4.1, RowGroup 1.3.1, RowReorder 1.3.3, Scroller 2.1.1, SearchBuilder 1.4.2, SearchPanes 2.1.2, Select 1.6.2, StateRestore 1.2.2 11 */ 12 13/*! DataTables 1.13.4 14 * ©2008-2023 SpryMedia Ltd - datatables.net/license 15 */ 16 17/** 18 * @summary DataTables 19 * @description Paginate, search and order HTML tables 20 * @version 1.13.4 21 * @author SpryMedia Ltd 22 * @contact www.datatables.net 23 * @copyright SpryMedia Ltd. 24 * 25 * This source file is free software, available under the following license: 26 * MIT license - http://datatables.net/license 27 * 28 * This source file is distributed in the hope that it will be useful, but 29 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 30 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 31 * 32 * For details please refer to: http://www.datatables.net 33 */ 34 35/*jslint evil: true, undef: true, browser: true */ 36/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ 37 38(function( factory ) { 39 "use strict"; 40 41 if ( typeof define === 'function' && define.amd ) { 42 // AMD 43 define( ['jquery'], function ( $ ) { 44 return factory( $, window, document ); 45 } ); 46 } 47 else if ( typeof exports === 'object' ) { 48 // CommonJS 49 // jQuery's factory checks for a global window - if it isn't present then it 50 // returns a factory function that expects the window object 51 var jq = require('jquery'); 52 53 if (typeof window !== 'undefined') { 54 module.exports = function (root, $) { 55 if ( ! root ) { 56 // CommonJS environments without a window global must pass a 57 // root. This will give an error otherwise 58 root = window; 59 } 60 61 if ( ! $ ) { 62 $ = jq( root ); 63 } 64 65 return factory( $, root, root.document ); 66 }; 67 } 68 else { 69 return factory( jq, window, window.document ); 70 } 71 } 72 else { 73 // Browser 74 window.DataTable = factory( jQuery, window, document ); 75 } 76} 77(function( $, window, document, undefined ) { 78 "use strict"; 79 80 81 var DataTable = function ( selector, options ) 82 { 83 // Check if called with a window or jQuery object for DOM less applications 84 // This is for backwards compatibility 85 if (DataTable.factory(selector, options)) { 86 return DataTable; 87 } 88 89 // When creating with `new`, create a new DataTable, returning the API instance 90 if (this instanceof DataTable) { 91 return $(selector).DataTable(options); 92 } 93 else { 94 // Argument switching 95 options = selector; 96 } 97 98 /** 99 * Perform a jQuery selector action on the table's TR elements (from the tbody) and 100 * return the resulting jQuery object. 101 * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on 102 * @param {object} [oOpts] Optional parameters for modifying the rows to be included 103 * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter 104 * criterion ("applied") or all TR elements (i.e. no filter). 105 * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. 106 * Can be either 'current', whereby the current sorting of the table is used, or 107 * 'original' whereby the original order the data was read into the table is used. 108 * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page 109 * ("current") or not ("all"). If 'current' is given, then order is assumed to be 110 * 'current' and filter is 'applied', regardless of what they might be given as. 111 * @returns {object} jQuery object, filtered by the given selector. 112 * @dtopt API 113 * @deprecated Since v1.10 114 * 115 * @example 116 * $(document).ready(function() { 117 * var oTable = $('#example').dataTable(); 118 * 119 * // Highlight every second row 120 * oTable.$('tr:odd').css('backgroundColor', 'blue'); 121 * } ); 122 * 123 * @example 124 * $(document).ready(function() { 125 * var oTable = $('#example').dataTable(); 126 * 127 * // Filter to rows with 'Webkit' in them, add a background colour and then 128 * // remove the filter, thus highlighting the 'Webkit' rows only. 129 * oTable.fnFilter('Webkit'); 130 * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); 131 * oTable.fnFilter(''); 132 * } ); 133 */ 134 this.$ = function ( sSelector, oOpts ) 135 { 136 return this.api(true).$( sSelector, oOpts ); 137 }; 138 139 140 /** 141 * Almost identical to $ in operation, but in this case returns the data for the matched
142 * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes 143 * rather than any descendants, so the data can be obtained for the row/cell. If matching 144 * rows are found, the data returned is the original data array/object that was used to 145 * create the row (or a generated array if from a DOM source). 146 * 147 * This method is often useful in-combination with $ where both functions are given the 148 * same parameters and the array indexes will match identically. 149 * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on 150 * @param {object} [oOpts] Optional parameters for modifying the rows to be included 151 * @param {string} [oOpts.filter=none] Select elements that meet the current filter 152 * criterion ("applied") or all elements (i.e. no filter). 153 * @param {string} [oOpts.order=current] Order of the data in the processed array. 154 * Can be either 'current', whereby the current sorting of the table is used, or 155 * 'original' whereby the original order the data was read into the table is used. 156 * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page 157 * ("current") or not ("all"). If 'current' is given, then order is assumed to be 158 * 'current' and filter is 'applied', regardless of what they might be given as. 159 * @returns {array} Data for the matched elements. If any elements, as a result of the 160 * selector, were not TR, TD or TH elements in the DataTable, they will have a null 161 * entry in the array. 162 * @dtopt API 163 * @deprecated Since v1.10 164 * 165 * @example 166 * $(document).ready(function() { 167 * var oTable = $('#example').dataTable(); 168 * 169 * // Get the data from the first row in the table 170 * var data = oTable._('tr:first'); 171 * 172 * // Do something useful with the data 173 * alert( "First cell is: "+data[0] ); 174 * } ); 175 * 176 * @example 177 * $(document).ready(function() { 178 * var oTable = $('#example').dataTable(); 179 * 180 * // Filter to 'Webkit' and get all data for 181 * oTable.fnFilter('Webkit'); 182 * var data = oTable._('tr', {"search": "applied"}); 183 * 184 * // Do something with the data 185 * alert( data.length+" rows matched the search" ); 186 * } ); 187 */ 188 this._ = function ( sSelector, oOpts ) 189 { 190 return this.api(true).rows( sSelector, oOpts ).data(); 191 }; 192 193 194 /** 195 * Create a DataTables Api instance, with the currently selected tables for 196 * the Api's context. 197 * @param {boolean} [traditional=false] Set the API instance's context to be 198 * only the table referred to by the `DataTable.ext.iApiIndex` option, as was 199 * used in the API presented by DataTables 1.9- (i.e. the traditional mode), 200 * or if all tables captured in the jQuery object should be used. 201 * @return {DataTables.Api} 202 */ 203 this.api = function ( traditional ) 204 { 205 return traditional ? 206 new _Api( 207 _fnSettingsFromNode( this[ _ext.iApiIndex ] ) 208 ) : 209 new _Api( this ); 210 }; 211 212 213 /** 214 * Add a single new row or multiple rows of data to the table. Please note 215 * that this is suitable for client-side processing only - if you are using 216 * server-side processing (i.e. "bServerSide": true), then to add data, you 217 * must add it to the data source, i.e. the server-side, through an Ajax call. 218 * @param {array|object} data The data to be added to the table. This can be: 219 * <ul> 220 * <li>1D array of data - add a single row with the data provided</li> 221 * <li>2D array of arrays - add multiple rows in a single call</li> 222 * <li>object - data object when using <i>mData</i></li> 223 * <li>array of objects - multiple data objects when using <i>mData</i></li> 224 * </ul> 225 * @param {bool} [redraw=true] redraw the table or not 226 * @returns {array} An array of integers, representing the list of indexes in 227 * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to 228 * the table. 229 * @dtopt API 230 * @deprecated Since v1.10 231 * 232 * @example 233 * // Global var for counter 234 * var giCount = 2; 235 * 236 * $(document).ready(function() { 237 * $('#example').dataTable(); 238 * } ); 239 * 240 * function fnClickAddRow() { 241 * $('#example').dataTable().fnAddData( [ 242 * giCount+".1", 243 * giCount+".2", 244 * giCount+".3", 245 * giCount+".4" ] 246 * ); 247 * 248 * giCount++; 249 * } 250 */ 251 this.fnAddData = function( data, redraw ) 252 { 253 var api = this.api( true ); 254 255 /* Check if we want to add multiple rows or not */ 256 var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ? 257 api.rows.add( data ) : 258 api.row.add( data ); 259 260 if ( redraw === undefined || redraw ) { 261 api.draw(); 262 } 263 264 return rows.flatten().toArray(); 265 }; 266 267 268 /** 269 * This function will make DataTables recalculate the column sizes, based on the data 270 * contained in the table and the sizes applied to the columns (in the DOM, CSS or 271 * through the sWidth parameter). This can be useful when the width of the table's
vendor: 200,873 bytes, lines 272-7019
272 * parent element changes (for example a window resize). 273 * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to 274 * @dtopt API 275 * @deprecated Since v1.10 276 * 277 * @example 278 * $(document).ready(function() { 279 * var oTable = $('#example').dataTable( { 280 * "sScrollY": "200px", 281 * "bPaginate": false 282 * } ); 283 * 284 * $(window).on('resize', function () { 285 * oTable.fnAdjustColumnSizing(); 286 * } ); 287 * } ); 288 */ 289 this.fnAdjustColumnSizing = function ( bRedraw ) 290 { 291 var api = this.api( true ).columns.adjust(); 292 var settings = api.settings()[0]; 293 var scroll = settings.oScroll; 294 295 if ( bRedraw === undefined || bRedraw ) { 296 api.draw( false ); 297 } 298 else if ( scroll.sX !== "" || scroll.sY !== "" ) { 299 /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ 300 _fnScrollDraw( settings ); 301 } 302 }; 303 304 305 /** 306 * Quickly and simply clear a table 307 * @param {bool} [bRedraw=true] redraw the table or not 308 * @dtopt API 309 * @deprecated Since v1.10 310 * 311 * @example 312 * $(document).ready(function() { 313 * var oTable = $('#example').dataTable(); 314 * 315 * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) 316 * oTable.fnClearTable(); 317 * } ); 318 */ 319 this.fnClearTable = function( bRedraw ) 320 { 321 var api = this.api( true ).clear(); 322 323 if ( bRedraw === undefined || bRedraw ) { 324 api.draw(); 325 } 326 }; 327 328 329 /** 330 * The exact opposite of 'opening' a row, this function will close any rows which 331 * are currently 'open'. 332 * @param {node} nTr the table row to 'close' 333 * @returns {int} 0 on success, or 1 if failed (can't find the row) 334 * @dtopt API 335 * @deprecated Since v1.10 336 * 337 * @example 338 * $(document).ready(function() { 339 * var oTable; 340 * 341 * // 'open' an information row when a row is clicked on 342 * $('#example tbody tr').click( function () { 343 * if ( oTable.fnIsOpen(this) ) { 344 * oTable.fnClose( this ); 345 * } else { 346 * oTable.fnOpen( this, "Temporary row opened", "info_row" ); 347 * } 348 * } ); 349 * 350 * oTable = $('#example').dataTable(); 351 * } ); 352 */ 353 this.fnClose = function( nTr ) 354 { 355 this.api( true ).row( nTr ).child.hide(); 356 }; 357 358 359 /** 360 * Remove a row for the table 361 * @param {mixed} target The index of the row from aoData to be deleted, or 362 * the TR element you want to delete 363 * @param {function|null} [callBack] Callback function 364 * @param {bool} [redraw=true] Redraw the table or not 365 * @returns {array} The row that was deleted 366 * @dtopt API 367 * @deprecated Since v1.10 368 * 369 * @example 370 * $(document).ready(function() { 371 * var oTable = $('#example').dataTable(); 372 * 373 * // Immediately remove the first row 374 * oTable.fnDeleteRow( 0 ); 375 * } ); 376 */ 377 this.fnDeleteRow = function( target, callback, redraw ) 378 { 379 var api = this.api( true ); 380 var rows = api.rows( target ); 381 var settings = rows.settings()[0]; 382 var data = settings.aoData[ rows[0][0] ]; 383 384 rows.remove(); 385 386 if ( callback ) { 387 callback.call( this, settings, data ); 388 } 389 390 if ( redraw === undefined || redraw ) { 391 api.draw(); 392 } 393 394 return data; 395 }; 396 397 398 /** 399 * Restore the table to it's original state in the DOM by removing all of DataTables 400 * enhancements, alterations to the DOM structure of the table and event listeners. 401 * @param {boolean} [remove=false] Completely remove the table from the DOM 402 * @dtopt API 403 * @deprecated Since v1.10 404 * 405 * @example 406 * $(document).ready(function() { 407 * // This example is fairly pointless in reality, but shows how fnDestroy can be used 408 * var oTable = $('#example').dataTable(); 409 * oTable.fnDestroy(); 410 * } ); 411 */ 412 this.fnDestroy = function ( remove ) 413 { 414 this.api( true ).destroy( remove ); 415 }; 416 417 418 /** 419 * Redraw the table 420 * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. 421 * @dtopt API 422 * @deprecated Since v1.10 423 * 424 * @example 425 * $(document).ready(function() { 426 * var oTable = $('#example').dataTable(); 427 * 428 * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) 429 * oTable.fnDraw(); 430 * } ); 431 */ 432 this.fnDraw = function( complete ) 433 { 434 // Note that this isn't an exact match to the old call to _fnDraw - it takes 435 // into account the new data, but can hold position. 436 this.api( true ).draw( complete ); 437 }; 438 439 440 /** 441 * Filter the input based on data 442 * @param {string} sInput String to filter the table on 443 * @param {int|null} [iColumn] Column to limit filtering to 444 * @param {bool} [bRegex=false] Treat as regular expression or not 445 * @param {bool} [bSmart=true] Perform smart filtering or not 446 * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) 447 * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) 448 * @dtopt API 449 * @deprecated Since v1.10 450 * 451 * @example 452 * $(document).ready(function() { 453 * var oTable = $('#example').dataTable(); 454 * 455 * // Sometime later - filter... 456 * oTable.fnFilter( 'test string' ); 457 * } ); 458 */ 459 this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) 460 { 461 var api = this.api( true ); 462 463 if ( iColumn === null || iColumn === undefined ) { 464 api.search( sInput, bRegex, bSmart, bCaseInsensitive ); 465 } 466 else { 467 api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); 468 } 469 470 api.draw(); 471 }; 472 473 474 /** 475 * Get the data for the whole table, an individual row or an individual cell based on the 476 * provided parameters. 477 * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as 478 * a TR node then the data source for the whole row will be returned. If given as a 479 * TD/TH cell node then iCol will be automatically calculated and the data for the 480 * cell returned. If given as an integer, then this is treated as the aoData internal 481 * data index for the row (see fnGetPosition) and the data for that row used. 482 * @param {int} [col] Optional column index that you want the data of. 483 * @returns {array|object|string} If mRow is undefined, then the data for all rows is 484 * returned. If mRow is defined, just data for that row, and is iCol is 485 * defined, only data for the designated cell is returned. 486 * @dtopt API 487 * @deprecated Since v1.10 488 * 489 * @example 490 * // Row data 491 * $(document).ready(function() { 492 * oTable = $('#example').dataTable(); 493 * 494 * oTable.$('tr').click( function () { 495 * var data = oTable.fnGetData( this ); 496 * // ... do something with the array / object of data for the row 497 * } ); 498 * } ); 499 * 500 * @example 501 * // Individual cell data 502 * $(document).ready(function() { 503 * oTable = $('#example').dataTable(); 504 * 505 * oTable.$('td').click( function () { 506 * var sData = oTable.fnGetData( this ); 507 * alert( 'The cell clicked on had the value of '+sData ); 508 * } ); 509 * } ); 510 */ 511 this.fnGetData = function( src, col ) 512 { 513 var api = this.api( true ); 514 515 if ( src !== undefined ) { 516 var type = src.nodeName ? src.nodeName.toLowerCase() : ''; 517 518 return col !== undefined || type == 'td' || type == 'th' ? 519 api.cell( src, col ).data() : 520 api.row( src ).data() || null; 521 } 522 523 return api.data().toArray(); 524 }; 525 526 527 /** 528 * Get an array of the TR nodes that are used in the table's body. Note that you will 529 * typically want to use the '$' API method in preference to this as it is more 530 * flexible. 531 * @param {int} [iRow] Optional row index for the TR element you want 532 * @returns {array|node} If iRow is undefined, returns an array of all TR elements 533 * in the table's body, or iRow is defined, just the TR element requested. 534 * @dtopt API 535 * @deprecated Since v1.10 536 * 537 * @example 538 * $(document).ready(function() { 539 * var oTable = $('#example').dataTable(); 540 * 541 * // Get the nodes from the table 542 * var nNodes = oTable.fnGetNodes( ); 543 * } ); 544 */ 545 this.fnGetNodes = function( iRow ) 546 { 547 var api = this.api( true ); 548 549 return iRow !== undefined ? 550 api.row( iRow ).node() : 551 api.rows().nodes().flatten().toArray(); 552 }; 553 554 555 /** 556 * Get the array indexes of a particular cell from it's DOM element 557 * and column index including hidden columns 558 * @param {node} node this can either be a TR, TD or TH in the table's body 559 * @returns {int} If nNode is given as a TR, then a single index is returned, or 560 * if given as a cell, an array of [row index, column index (visible), 561 * column index (all)] is given. 562 * @dtopt API 563 * @deprecated Since v1.10 564 * 565 * @example 566 * $(document).ready(function() { 567 * $('#example tbody td').click( function () { 568 * // Get the position of the current data from the node 569 * var aPos = oTable.fnGetPosition( this ); 570 * 571 * // Get the data array for this row 572 * var aData = oTable.fnGetData( aPos[0] ); 573 * 574 * // Update the data array and return the value 575 * aData[ aPos[1] ] = 'clicked'; 576 * this.innerHTML = 'clicked'; 577 * } ); 578 * 579 * // Init DataTables 580 * oTable = $('#example').dataTable(); 581 * } ); 582 */ 583 this.fnGetPosition = function( node ) 584 { 585 var api = this.api( true ); 586 var nodeName = node.nodeName.toUpperCase(); 587 588 if ( nodeName == 'TR' ) { 589 return api.row( node ).index(); 590 } 591 else if ( nodeName == 'TD' || nodeName == 'TH' ) { 592 var cell = api.cell( node ).index(); 593 594 return [ 595 cell.row, 596 cell.columnVisible, 597 cell.column 598 ]; 599 } 600 return null; 601 }; 602 603 604 /** 605 * Check to see if a row is 'open' or not. 606 * @param {node} nTr the table row to check 607 * @returns {boolean} true if the row is currently open, false otherwise 608 * @dtopt API 609 * @deprecated Since v1.10 610 * 611 * @example 612 * $(document).ready(function() { 613 * var oTable; 614 * 615 * // 'open' an information row when a row is clicked on 616 * $('#example tbody tr').click( function () { 617 * if ( oTable.fnIsOpen(this) ) { 618 * oTable.fnClose( this ); 619 * } else { 620 * oTable.fnOpen( this, "Temporary row opened", "info_row" ); 621 * } 622 * } ); 623 * 624 * oTable = $('#example').dataTable(); 625 * } ); 626 */ 627 this.fnIsOpen = function( nTr ) 628 { 629 return this.api( true ).row( nTr ).child.isShown(); 630 }; 631 632 633 /** 634 * This function will place a new row directly after a row which is currently 635 * on display on the page, with the HTML contents that is passed into the 636 * function. This can be used, for example, to ask for confirmation that a 637 * particular record should be deleted. 638 * @param {node} nTr The table row to 'open' 639 * @param {string|node|jQuery} mHtml The HTML to put into the row 640 * @param {string} sClass Class to give the new TD cell 641 * @returns {node} The row opened. Note that if the table row passed in as the 642 * first parameter, is not found in the table, this method will silently 643 * return. 644 * @dtopt API 645 * @deprecated Since v1.10 646 * 647 * @example 648 * $(document).ready(function() { 649 * var oTable; 650 * 651 * // 'open' an information row when a row is clicked on 652 * $('#example tbody tr').click( function () { 653 * if ( oTable.fnIsOpen(this) ) { 654 * oTable.fnClose( this ); 655 * } else { 656 * oTable.fnOpen( this, "Temporary row opened", "info_row" ); 657 * } 658 * } ); 659 * 660 * oTable = $('#example').dataTable(); 661 * } ); 662 */ 663 this.fnOpen = function( nTr, mHtml, sClass ) 664 { 665 return this.api( true ) 666 .row( nTr ) 667 .child( mHtml, sClass ) 668 .show() 669 .child()[0]; 670 }; 671 672 673 /** 674 * Change the pagination - provides the internal logic for pagination in a simple API 675 * function. With this function you can have a DataTables table go to the next, 676 * previous, first or last pages. 677 * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" 678 * or page number to jump to (integer), note that page 0 is the first page. 679 * @param {bool} [bRedraw=true] Redraw the table or not 680 * @dtopt API 681 * @deprecated Since v1.10 682 * 683 * @example 684 * $(document).ready(function() { 685 * var oTable = $('#example').dataTable(); 686 * oTable.fnPageChange( 'next' ); 687 * } ); 688 */ 689 this.fnPageChange = function ( mAction, bRedraw ) 690 { 691 var api = this.api( true ).page( mAction ); 692 693 if ( bRedraw === undefined || bRedraw ) { 694 api.draw(false); 695 } 696 }; 697 698 699 /** 700 * Show a particular column 701 * @param {int} iCol The column whose display should be changed 702 * @param {bool} bShow Show (true) or hide (false) the column 703 * @param {bool} [bRedraw=true] Redraw the table or not 704 * @dtopt API 705 * @deprecated Since v1.10 706 * 707 * @example 708 * $(document).ready(function() { 709 * var oTable = $('#example').dataTable(); 710 * 711 * // Hide the second column after initialisation 712 * oTable.fnSetColumnVis( 1, false ); 713 * } ); 714 */ 715 this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) 716 { 717 var api = this.api( true ).column( iCol ).visible( bShow ); 718 719 if ( bRedraw === undefined || bRedraw ) { 720 api.columns.adjust().draw(); 721 } 722 }; 723 724 725 /** 726 * Get the settings for a particular table for external manipulation 727 * @returns {object} DataTables settings object. See 728 * {@link DataTable.models.oSettings} 729 * @dtopt API 730 * @deprecated Since v1.10 731 * 732 * @example 733 * $(document).ready(function() { 734 * var oTable = $('#example').dataTable(); 735 * var oSettings = oTable.fnSettings(); 736 * 737 * // Show an example parameter from the settings 738 * alert( oSettings._iDisplayStart ); 739 * } ); 740 */ 741 this.fnSettings = function() 742 { 743 return _fnSettingsFromNode( this[_ext.iApiIndex] ); 744 }; 745 746 747 /** 748 * Sort the table by a particular column 749 * @param {int} iCol the data index to sort on. Note that this will not match the 750 * 'display index' if you have hidden data entries 751 * @dtopt API 752 * @deprecated Since v1.10 753 * 754 * @example 755 * $(document).ready(function() { 756 * var oTable = $('#example').dataTable(); 757 * 758 * // Sort immediately with columns 0 and 1 759 * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); 760 * } ); 761 */ 762 this.fnSort = function( aaSort ) 763 { 764 this.api( true ).order( aaSort ).draw(); 765 }; 766 767 768 /** 769 * Attach a sort listener to an element for a given column 770 * @param {node} nNode the element to attach the sort listener to 771 * @param {int} iColumn the column that a click on this node will sort on 772 * @param {function} [fnCallback] callback function when sort is run 773 * @dtopt API 774 * @deprecated Since v1.10 775 * 776 * @example 777 * $(document).ready(function() { 778 * var oTable = $('#example').dataTable(); 779 * 780 * // Sort on column 1, when 'sorter' is clicked on 781 * oTable.fnSortListener( document.getElementById('sorter'), 1 ); 782 * } ); 783 */ 784 this.fnSortListener = function( nNode, iColumn, fnCallback ) 785 { 786 this.api( true ).order.listener( nNode, iColumn, fnCallback ); 787 }; 788 789 790 /** 791 * Update a table cell or row - this method will accept either a single value to 792 * update the cell with, an array of values with one element for each column or 793 * an object in the same format as the original data source. The function is 794 * self-referencing in order to make the multi column updates easier. 795 * @param {object|array|string} mData Data to update the cell/row with 796 * @param {node|int} mRow TR element you want to update or the aoData index 797 * @param {int} [iColumn] The column to update, give as null or undefined to 798 * update a whole row. 799 * @param {bool} [bRedraw=true] Redraw the table or not 800 * @param {bool} [bAction=true] Perform pre-draw actions or not 801 * @returns {int} 0 on success, 1 on error 802 * @dtopt API 803 * @deprecated Since v1.10 804 * 805 * @example 806 * $(document).ready(function() { 807 * var oTable = $('#example').dataTable(); 808 * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell 809 * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row 810 * } ); 811 */ 812 this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) 813 { 814 var api = this.api( true ); 815 816 if ( iColumn === undefined || iColumn === null ) { 817 api.row( mRow ).data( mData ); 818 } 819 else { 820 api.cell( mRow, iColumn ).data( mData ); 821 } 822 823 if ( bAction === undefined || bAction ) { 824 api.columns.adjust(); 825 } 826 827 if ( bRedraw === undefined || bRedraw ) { 828 api.draw(); 829 } 830 return 0; 831 }; 832 833 834 /** 835 * Provide a common method for plug-ins to check the version of DataTables being used, in order 836 * to ensure compatibility. 837 * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the 838 * formats "X" and "X.Y" are also acceptable. 839 * @returns {boolean} true if this version of DataTables is greater or equal to the required 840 * version, or false if this version of DataTales is not suitable 841 * @method 842 * @dtopt API 843 * @deprecated Since v1.10 844 * 845 * @example 846 * $(document).ready(function() { 847 * var oTable = $('#example').dataTable(); 848 * alert( oTable.fnVersionCheck( '1.9.0' ) ); 849 * } ); 850 */ 851 this.fnVersionCheck = _ext.fnVersionCheck; 852 853 854 var _that = this; 855 var emptyInit = options === undefined; 856 var len = this.length; 857 858 if ( emptyInit ) { 859 options = {}; 860 } 861 862 this.oApi = this.internal = _ext.internal; 863 864 // Extend with old style plug-in API methods 865 for ( var fn in DataTable.ext.internal ) { 866 if ( fn ) { 867 this[fn] = _fnExternApiFunc(fn); 868 } 869 } 870 871 this.each(function() { 872 // For each initialisation we want to give it a clean initialisation 873 // object that can be bashed around 874 var o = {}; 875 var oInit = len > 1 ? // optimisation for single table case 876 _fnExtend( o, options, true ) : 877 options; 878 879 /*global oInit,_that,emptyInit*/ 880 var i=0, iLen, j, jLen, k, kLen; 881 var sId = this.getAttribute( 'id' ); 882 var bInitHandedOff = false; 883 var defaults = DataTable.defaults; 884 var $this = $(this); 885 886 887 /* Sanity check */ 888 if ( this.nodeName.toLowerCase() != 'table' ) 889 { 890 _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); 891 return; 892 } 893 894 /* Backwards compatibility for the defaults */ 895 _fnCompatOpts( defaults ); 896 _fnCompatCols( defaults.column ); 897 898 /* Convert the camel-case defaults to Hungarian */ 899 _fnCamelToHungarian( defaults, defaults, true ); 900 _fnCamelToHungarian( defaults.column, defaults.column, true ); 901 902 /* Setting up the initialisation object */ 903 _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); 904 905 906 907 /* Check to see if we are re-initialising a table */ 908 var allSettings = DataTable.settings; 909 for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) 910 { 911 var s = allSettings[i]; 912 913 /* Base check on table node */ 914 if ( 915 s.nTable == this || 916 (s.nTHead && s.nTHead.parentNode == this) || 917 (s.nTFoot && s.nTFoot.parentNode == this) 918 ) { 919 var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; 920 var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; 921 922 if ( emptyInit || bRetrieve ) 923 { 924 return s.oInstance; 925 } 926 else if ( bDestroy ) 927 { 928 s.oInstance.fnDestroy(); 929 break; 930 } 931 else 932 { 933 _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); 934 return; 935 } 936 } 937 938 /* If the element we are initialising has the same ID as a table which was previously 939 * initialised, but the table nodes don't match (from before) then we destroy the old 940 * instance by simply deleting it. This is under the assumption that the table has been 941 * destroyed by other methods. Anyone using non-id selectors will need to do this manually 942 */ 943 if ( s.sTableId == this.id ) 944 { 945 allSettings.splice( i, 1 ); 946 break; 947 } 948 } 949 950 /* Ensure the table has an ID - required for accessibility */ 951 if ( sId === null || sId === "" ) 952 { 953 sId = "DataTables_Table_"+(DataTable.ext._unique++); 954 this.id = sId; 955 } 956 957 /* Create the settings object for this table and set some of the default parameters */ 958 var oSettings = $.extend( true, {}, DataTable.models.oSettings, { 959 "sDestroyWidth": $this[0].style.width, 960 "sInstance": sId, 961 "sTableId": sId 962 } ); 963 oSettings.nTable = this; 964 oSettings.oApi = _that.internal; 965 oSettings.oInit = oInit; 966 967 allSettings.push( oSettings ); 968 969 // Need to add the instance after the instance after the settings object has been added 970 // to the settings array, so we can self reference the table instance if more than one 971 oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); 972 973 // Backwards compatibility, before we apply all the defaults 974 _fnCompatOpts( oInit ); 975 _fnLanguageCompat( oInit.oLanguage ); 976 977 // If the length menu is given, but the init display length is not, use the length menu 978 if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) 979 { 980 oInit.iDisplayLength = Array.isArray( oInit.aLengthMenu[0] ) ? 981 oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; 982 } 983 984 // Apply the defaults and init options to make a single init object will all 985 // options defined from defaults and instance options. 986 oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); 987 988 989 // Map the initialisation options onto the settings object 990 _fnMap( oSettings.oFeatures, oInit, [ 991 "bPaginate", 992 "bLengthChange", 993 "bFilter", 994 "bSort", 995 "bSortMulti", 996 "bInfo", 997 "bProcessing", 998 "bAutoWidth", 999 "bSortClasses", 1000 "bServerSide", 1001 "bDeferRender" 1002 ] ); 1003 _fnMap( oSettings, oInit, [ 1004 "asStripeClasses", 1005 "ajax", 1006 "fnServerData", 1007 "fnFormatNumber", 1008 "sServerMethod", 1009 "aaSorting", 1010 "aaSortingFixed", 1011 "aLengthMenu", 1012 "sPaginationType", 1013 "sAjaxSource", 1014 "sAjaxDataProp", 1015 "iStateDuration", 1016 "sDom", 1017 "bSortCellsTop", 1018 "iTabIndex", 1019 "fnStateLoadCallback", 1020 "fnStateSaveCallback", 1021 "renderer", 1022 "searchDelay", 1023 "rowId", 1024 [ "iCookieDuration", "iStateDuration" ], // backwards compat 1025 [ "oSearch", "oPreviousSearch" ], 1026 [ "aoSearchCols", "aoPreSearchCols" ], 1027 [ "iDisplayLength", "_iDisplayLength" ] 1028 ] ); 1029 _fnMap( oSettings.oScroll, oInit, [ 1030 [ "sScrollX", "sX" ], 1031 [ "sScrollXInner", "sXInner" ], 1032 [ "sScrollY", "sY" ], 1033 [ "bScrollCollapse", "bCollapse" ] 1034 ] ); 1035 _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); 1036 1037 /* Callback functions which are array driven */ 1038 _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' ); 1039 _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' ); 1040 _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' ); 1041 _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' ); 1042 _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' ); 1043 _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' ); 1044 _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' ); 1045 _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' ); 1046 _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' ); 1047 _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' ); 1048 _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' ); 1049 1050 oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); 1051 1052 /* Browser support detection */ 1053 _fnBrowserDetect( oSettings ); 1054 1055 var oClasses = oSettings.oClasses; 1056 1057 $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); 1058 $this.addClass( oClasses.sTable ); 1059 1060 1061 if ( oSettings.iInitDisplayStart === undefined ) 1062 { 1063 /* Display start point, taking into account the save saving */ 1064 oSettings.iInitDisplayStart = oInit.iDisplayStart; 1065 oSettings._iDisplayStart = oInit.iDisplayStart; 1066 } 1067 1068 if ( oInit.iDeferLoading !== null ) 1069 { 1070 oSettings.bDeferLoading = true; 1071 var tmp = Array.isArray( oInit.iDeferLoading ); 1072 oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; 1073 oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; 1074 } 1075 1076 /* Language definitions */ 1077 var oLanguage = oSettings.oLanguage; 1078 $.extend( true, oLanguage, oInit.oLanguage ); 1079 1080 if ( oLanguage.sUrl ) 1081 { 1082 /* Get the language definitions from a file - because this Ajax call makes the language 1083 * get async to the remainder of this function we use bInitHandedOff to indicate that 1084 * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor 1085 */ 1086 $.ajax( { 1087 dataType: 'json', 1088 url: oLanguage.sUrl, 1089 success: function ( json ) { 1090 _fnCamelToHungarian( defaults.oLanguage, json ); 1091 _fnLanguageCompat( json ); 1092 $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); 1093 1094 _fnCallbackFire( oSettings, null, 'i18n', [oSettings]); 1095 _fnInitialise( oSettings ); 1096 }, 1097 error: function () { 1098 // Error occurred loading language file, continue on as best we can 1099 _fnInitialise( oSettings ); 1100 } 1101 } ); 1102 bInitHandedOff = true; 1103 } 1104 else { 1105 _fnCallbackFire( oSettings, null, 'i18n', [oSettings]); 1106 } 1107 1108 /* 1109 * Stripes 1110 */ 1111 if ( oInit.asStripeClasses === null ) 1112 { 1113 oSettings.asStripeClasses =[ 1114 oClasses.sStripeOdd, 1115 oClasses.sStripeEven 1116 ]; 1117 } 1118 1119 /* Remove row stripe classes if they are already on the table row */ 1120 var stripeClasses = oSettings.asStripeClasses; 1121 var rowOne = $this.children('tbody').find('tr').eq(0); 1122 if ( $.inArray( true, $.map( stripeClasses, function(el, i) { 1123 return rowOne.hasClass(el); 1124 } ) ) !== -1 ) { 1125 $('tbody tr', this).removeClass( stripeClasses.join(' ') ); 1126 oSettings.asDestroyStripes = stripeClasses.slice(); 1127 } 1128 1129 /* 1130 * Columns 1131 * See if we should load columns automatically or use defined ones 1132 */ 1133 var anThs = []; 1134 var aoColumnsInit; 1135 var nThead = this.getElementsByTagName('thead'); 1136 if ( nThead.length !== 0 ) 1137 { 1138 _fnDetectHeader( oSettings.aoHeader, nThead[0] ); 1139 anThs = _fnGetUniqueThs( oSettings ); 1140 } 1141 1142 /* If not given a column array, generate one with nulls */ 1143 if ( oInit.aoColumns === null ) 1144 { 1145 aoColumnsInit = []; 1146 for ( i=0, iLen=anThs.length ; i<iLen ; i++ ) 1147 { 1148 aoColumnsInit.push( null ); 1149 } 1150 } 1151 else 1152 { 1153 aoColumnsInit = oInit.aoColumns; 1154 } 1155 1156 /* Add the columns */ 1157 for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ ) 1158 { 1159 _fnAddColumn( oSettings, anThs ? anThs[i] : null ); 1160 } 1161 1162 /* Apply the column definitions */ 1163 _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { 1164 _fnColumnOptions( oSettings, iCol, oDef ); 1165 } ); 1166 1167 /* HTML5 attribute detection - build an mData object automatically if the 1168 * attributes are found 1169 */ 1170 if ( rowOne.length ) { 1171 var a = function ( cell, name ) { 1172 return cell.getAttribute( 'data-'+name ) !== null ? name : null; 1173 }; 1174 1175 $( rowOne[0] ).children('th, td').each( function (i, cell) { 1176 var col = oSettings.aoColumns[i]; 1177 1178 if (! col) { 1179 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 1180 } 1181 1182 if ( col.mData === i ) { 1183 var sort = a( cell, 'sort' ) || a( cell, 'order' ); 1184 var filter = a( cell, 'filter' ) || a( cell, 'search' ); 1185 1186 if ( sort !== null || filter !== null ) { 1187 col.mData = { 1188 _: i+'.display', 1189 sort: sort !== null ? i+'.@data-'+sort : undefined, 1190 type: sort !== null ? i+'.@data-'+sort : undefined, 1191 filter: filter !== null ? i+'.@data-'+filter : undefined 1192 }; 1193 col._isArrayHost = true; 1194 1195 _fnColumnOptions( oSettings, i ); 1196 } 1197 } 1198 } ); 1199 } 1200 1201 var features = oSettings.oFeatures; 1202 var loadedInit = function () { 1203 /* 1204 * Sorting 1205 * @todo For modularisation (1.11) this needs to do into a sort start up handler 1206 */ 1207 1208 // If aaSorting is not defined, then we use the first indicator in asSorting 1209 // in case that has been altered, so the default sort reflects that option 1210 if ( oInit.aaSorting === undefined ) { 1211 var sorting = oSettings.aaSorting; 1212 for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { 1213 sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; 1214 } 1215 } 1216 1217 /* Do a first pass on the sorting classes (allows any size changes to be taken into 1218 * account, and also will apply sorting disabled classes if disabled 1219 */ 1220 _fnSortingClasses( oSettings ); 1221 1222 if ( features.bSort ) { 1223 _fnCallbackReg( oSettings, 'aoDrawCallback', function () { 1224 if ( oSettings.bSorted ) { 1225 var aSort = _fnSortFlatten( oSettings ); 1226 var sortedColumns = {}; 1227 1228 $.each( aSort, function (i, val) { 1229 sortedColumns[ val.src ] = val.dir; 1230 } ); 1231 1232 _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] ); 1233 _fnSortAria( oSettings ); 1234 } 1235 } ); 1236 } 1237 1238 _fnCallbackReg( oSettings, 'aoDrawCallback', function () { 1239 if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { 1240 _fnSortingClasses( oSettings ); 1241 } 1242 }, 'sc' ); 1243 1244 1245 /* 1246 * Final init 1247 * Cache the header, body and footer as required, creating them if needed 1248 */ 1249 1250 // Work around for Webkit bug 83867 - store the caption-side before removing from doc 1251 var captions = $this.children('caption').each( function () { 1252 this._captionSide = $(this).css('caption-side'); 1253 } ); 1254 1255 var thead = $this.children('thead'); 1256 if ( thead.length === 0 ) { 1257 thead = $('<thead/>').appendTo($this); 1258 } 1259 oSettings.nTHead = thead[0]; 1260 1261 var tbody = $this.children('tbody'); 1262 if ( tbody.length === 0 ) { 1263 tbody = $('<tbody/>').insertAfter(thead); 1264 } 1265 oSettings.nTBody = tbody[0]; 1266 1267 var tfoot = $this.children('tfoot'); 1268 if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { 1269 // If we are a scrolling table, and no footer has been given, then we need to create 1270 // a tfoot element for the caption element to be appended to 1271 tfoot = $('<tfoot/>').appendTo($this); 1272 } 1273 1274 if ( tfoot.length === 0 || tfoot.children().length === 0 ) { 1275 $this.addClass( oClasses.sNoFooter ); 1276 } 1277 else if ( tfoot.length > 0 ) { 1278 oSettings.nTFoot = tfoot[0]; 1279 _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); 1280 } 1281 1282 /* Check if there is data passing into the constructor */ 1283 if ( oInit.aaData ) { 1284 for ( i=0 ; i<oInit.aaData.length ; i++ ) { 1285 _fnAddData( oSettings, oInit.aaData[ i ] ); 1286 } 1287 } 1288 else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) { 1289 /* Grab the data from the page - only do this when deferred loading or no Ajax 1290 * source since there is no point in reading the DOM data if we are then going 1291 * to replace it with Ajax data 1292 */ 1293 _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') ); 1294 } 1295 1296 /* Copy the data index array */ 1297 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); 1298 1299 /* Initialisation complete - table can be drawn */ 1300 oSettings.bInitialised = true; 1301 1302 /* Check if we need to initialise the table (it might not have been handed off to the 1303 * language processor) 1304 */ 1305 if ( bInitHandedOff === false ) { 1306 _fnInitialise( oSettings ); 1307 } 1308 }; 1309 1310 /* Must be done after everything which can be overridden by the state saving! */ 1311 _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' ); 1312 1313 if ( oInit.bStateSave ) 1314 { 1315 features.bStateSave = true; 1316 _fnLoadState( oSettings, oInit, loadedInit ); 1317 } 1318 else { 1319 loadedInit(); 1320 } 1321 1322 } ); 1323 _that = null; 1324 return this; 1325 }; 1326 1327 1328 /* 1329 * It is useful to have variables which are scoped locally so only the 1330 * DataTables functions can access them and they don't leak into global space. 1331 * At the same time these functions are often useful over multiple files in the 1332 * core and API, so we list, or at least document, all variables which are used 1333 * by DataTables as private variables here. This also ensures that there is no 1334 * clashing of variable names and that they can easily referenced for reuse. 1335 */ 1336 1337 1338 // Defined else where 1339 // _selector_run 1340 // _selector_opts 1341 // _selector_first 1342 // _selector_row_indexes 1343 1344 var _ext; // DataTable.ext 1345 var _Api; // DataTable.Api 1346 var _api_register; // DataTable.Api.register 1347 var _api_registerPlural; // DataTable.Api.registerPlural 1348 1349 var _re_dic = {}; 1350 var _re_new_lines = /[\r\n\u2028]/g; 1351 var _re_html = /<.*?>/g; 1352 1353 // This is not strict ISO8601 - Date.parse() is quite lax, although 1354 // implementations differ between browsers. 1355 var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; 1356 1357 // Escape regular expression special characters 1358 var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); 1359 1360 // http://en.wikipedia.org/wiki/Foreign_exchange_market 1361 // - \u20BD - Russian ruble. 1362 // - \u20a9 - South Korean Won 1363 // - \u20BA - Turkish Lira 1364 // - \u20B9 - Indian Rupee 1365 // - R - Brazil (R$) and South Africa 1366 // - fr - Swiss Franc 1367 // - kr - Swedish krona, Norwegian krone and Danish krone 1368 // - \u2009 is thin space and \u202F is narrow no-break space, both used in many 1369 // - É - Bitcoin 1370 // - Î - Ethereum 1371 // standards as thousands separators. 1372 var _re_formatted_numeric = /['\u00A0,$£â¬Â¥%\u2009\u202F\u20BD\u20a9\u20BArfkÉÎ]/gi; 1373 1374 1375 var _empty = function ( d ) { 1376 return !d || d === true || d === '-' ? true : false; 1377 }; 1378 1379 1380 var _intVal = function ( s ) { 1381 var integer = parseInt( s, 10 ); 1382 return !isNaN(integer) && isFinite(s) ? integer : null; 1383 }; 1384 1385 // Convert from a formatted number with characters other than `.` as the 1386 // decimal place, to a Javascript number 1387 var _numToDecimal = function ( num, decimalPoint ) { 1388 // Cache created regular expressions for speed as this function is called often 1389 if ( ! _re_dic[ decimalPoint ] ) { 1390 _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); 1391 } 1392 return typeof num === 'string' && decimalPoint !== '.' ? 1393 num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : 1394 num; 1395 }; 1396 1397 1398 var _isNumber = function ( d, decimalPoint, formatted ) { 1399 let type = typeof d; 1400 var strType = type === 'string'; 1401 1402 if ( type === 'number' || type === 'bigint') { 1403 return true; 1404 } 1405 1406 // If empty return immediately so there must be a number if it is a 1407 // formatted string (this stops the string "k", or "kr", etc being detected 1408 // as a formatted number for currency 1409 if ( _empty( d ) ) { 1410 return true; 1411 } 1412 1413 if ( decimalPoint && strType ) { 1414 d = _numToDecimal( d, decimalPoint ); 1415 } 1416 1417 if ( formatted && strType ) { 1418 d = d.replace( _re_formatted_numeric, '' ); 1419 } 1420 1421 return !isNaN( parseFloat(d) ) && isFinite( d ); 1422 }; 1423 1424 1425 // A string without HTML in it can be considered to be HTML still 1426 var _isHtml = function ( d ) { 1427 return _empty( d ) || typeof d === 'string'; 1428 }; 1429 1430 1431 var _htmlNumeric = function ( d, decimalPoint, formatted ) { 1432 if ( _empty( d ) ) { 1433 return true; 1434 } 1435 1436 var html = _isHtml( d ); 1437 return ! html ? 1438 null : 1439 _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? 1440 true : 1441 null; 1442 }; 1443 1444 1445 var _pluck = function ( a, prop, prop2 ) { 1446 var out = []; 1447 var i=0, ien=a.length; 1448 1449 // Could have the test in the loop for slightly smaller code, but speed 1450 // is essential here 1451 if ( prop2 !== undefined ) { 1452 for ( ; i<ien ; i++ ) { 1453 if ( a[i] && a[i][ prop ] ) { 1454 out.push( a[i][ prop ][ prop2 ] ); 1455 } 1456 } 1457 } 1458 else { 1459 for ( ; i<ien ; i++ ) { 1460 if ( a[i] ) { 1461 out.push( a[i][ prop ] ); 1462 } 1463 } 1464 } 1465 1466 return out; 1467 }; 1468 1469 1470 // Basically the same as _pluck, but rather than looping over `a` we use `order` 1471 // as the indexes to pick from `a` 1472 var _pluck_order = function ( a, order, prop, prop2 ) 1473 { 1474 var out = []; 1475 var i=0, ien=order.length; 1476 1477 // Could have the test in the loop for slightly smaller code, but speed 1478 // is essential here 1479 if ( prop2 !== undefined ) { 1480 for ( ; i<ien ; i++ ) { 1481 if ( a[ order[i] ][ prop ] ) { 1482 out.push( a[ order[i] ][ prop ][ prop2 ] ); 1483 } 1484 } 1485 } 1486 else { 1487 for ( ; i<ien ; i++ ) { 1488 out.push( a[ order[i] ][ prop ] ); 1489 } 1490 } 1491 1492 return out; 1493 }; 1494 1495 1496 var _range = function ( len, start ) 1497 { 1498 var out = []; 1499 var end; 1500 1501 if ( start === undefined ) { 1502 start = 0; 1503 end = len; 1504 } 1505 else { 1506 end = start; 1507 start = len; 1508 } 1509 1510 for ( var i=start ; i<end ; i++ ) { 1511 out.push( i ); 1512 } 1513 1514 return out; 1515 }; 1516 1517 1518 var _removeEmpty = function ( a ) 1519 { 1520 var out = []; 1521 1522 for ( var i=0, ien=a.length ; i<ien ; i++ ) { 1523 if ( a[i] ) { // careful - will remove all falsy values! 1524 out.push( a[i] ); 1525 } 1526 } 1527 1528 return out; 1529 }; 1530 1531 1532 var _stripHtml = function ( d ) { 1533 return d.replace( _re_html, '' ); 1534 }; 1535 1536 1537 /** 1538 * Determine if all values in the array are unique. This means we can short 1539 * cut the _unique method at the cost of a single loop. A sorted array is used 1540 * to easily check the values. 1541 * 1542 * @param {array} src Source array 1543 * @return {boolean} true if all unique, false otherwise 1544 * @ignore 1545 */ 1546 var _areAllUnique = function ( src ) { 1547 if ( src.length < 2 ) { 1548 return true; 1549 } 1550 1551 var sorted = src.slice().sort(); 1552 var last = sorted[0]; 1553 1554 for ( var i=1, ien=sorted.length ; i<ien ; i++ ) { 1555 if ( sorted[i] === last ) { 1556 return false; 1557 } 1558 1559 last = sorted[i]; 1560 } 1561 1562 return true; 1563 }; 1564 1565 1566 /** 1567 * Find the unique elements in a source array. 1568 * 1569 * @param {array} src Source array 1570 * @return {array} Array of unique items 1571 * @ignore 1572 */ 1573 var _unique = function ( src ) 1574 { 1575 if ( _areAllUnique( src ) ) { 1576 return src.slice(); 1577 } 1578 1579 // A faster unique method is to use object keys to identify used values, 1580 // but this doesn't work with arrays or objects, which we must also 1581 // consider. See jsperf.com/compare-array-unique-versions/4 for more 1582 // information. 1583 var 1584 out = [], 1585 val, 1586 i, ien=src.length, 1587 j, k=0; 1588 1589 again: for ( i=0 ; i<ien ; i++ ) { 1590 val = src[i]; 1591 1592 for ( j=0 ; j<k ; j++ ) { 1593 if ( out[j] === val ) { 1594 continue again; 1595 } 1596 } 1597 1598 out.push( val ); 1599 k++; 1600 } 1601 1602 return out; 1603 }; 1604 1605 // Surprisingly this is faster than [].concat.apply 1606 // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 1607 var _flatten = function (out, val) { 1608 if (Array.isArray(val)) { 1609 for (var i=0 ; i<val.length ; i++) { 1610 _flatten(out, val[i]); 1611 } 1612 } 1613 else { 1614 out.push(val); 1615 } 1616 1617 return out; 1618 } 1619 1620 var _includes = function (search, start) { 1621 if (start === undefined) { 1622 start = 0; 1623 } 1624 1625 return this.indexOf(search, start) !== -1; 1626 }; 1627 1628 // Array.isArray polyfill. 1629 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray 1630 if (! Array.isArray) { 1631 Array.isArray = function(arg) { 1632 return Object.prototype.toString.call(arg) === '[object Array]'; 1633 }; 1634 } 1635 1636 if (! Array.prototype.includes) { 1637 Array.prototype.includes = _includes; 1638 } 1639 1640 // .trim() polyfill 1641 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim 1642 if (!String.prototype.trim) { 1643 String.prototype.trim = function () { 1644 return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 1645 }; 1646 } 1647 1648 if (! String.prototype.includes) { 1649 String.prototype.includes = _includes; 1650 } 1651 1652 /** 1653 * DataTables utility methods 1654 * 1655 * This namespace provides helper methods that DataTables uses internally to 1656 * create a DataTable, but which are not exclusively used only for DataTables. 1657 * These methods can be used by extension authors to save the duplication of 1658 * code. 1659 * 1660 * @namespace 1661 */ 1662 DataTable.util = { 1663 /** 1664 * Throttle the calls to a function. Arguments and context are maintained 1665 * for the throttled function. 1666 * 1667 * @param {function} fn Function to be called 1668 * @param {integer} freq Call frequency in mS 1669 * @return {function} Wrapped function 1670 */ 1671 throttle: function ( fn, freq ) { 1672 var 1673 frequency = freq !== undefined ? freq : 200, 1674 last, 1675 timer; 1676 1677 return function () { 1678 var 1679 that = this, 1680 now = +new Date(), 1681 args = arguments; 1682 1683 if ( last && now < last + frequency ) { 1684 clearTimeout( timer ); 1685 1686 timer = setTimeout( function () { 1687 last = undefined; 1688 fn.apply( that, args ); 1689 }, frequency ); 1690 } 1691 else { 1692 last = now; 1693 fn.apply( that, args ); 1694 } 1695 }; 1696 }, 1697 1698 1699 /** 1700 * Escape a string such that it can be used in a regular expression 1701 * 1702 * @param {string} val string to escape 1703 * @returns {string} escaped string 1704 */ 1705 escapeRegex: function ( val ) { 1706 return val.replace( _re_escape_regex, '\\$1' ); 1707 }, 1708 1709 /** 1710 * Create a function that will write to a nested object or array 1711 * @param {*} source JSON notation string 1712 * @returns Write function 1713 */ 1714 set: function ( source ) { 1715 if ( $.isPlainObject( source ) ) { 1716 /* Unlike get, only the underscore (global) option is used for for 1717 * setting data since we don't know the type here. This is why an object 1718 * option is not documented for `mData` (which is read/write), but it is 1719 * for `mRender` which is read only. 1720 */ 1721 return DataTable.util.set( source._ ); 1722 } 1723 else if ( source === null ) { 1724 // Nothing to do when the data source is null 1725 return function () {}; 1726 } 1727 else if ( typeof source === 'function' ) { 1728 return function (data, val, meta) { 1729 source( data, 'set', val, meta ); 1730 }; 1731 } 1732 else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || 1733 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) ) 1734 { 1735 // Like the get, we need to get data from a nested object 1736 var setData = function (data, val, src) { 1737 var a = _fnSplitObjNotation( src ), b; 1738 var aLast = a[a.length-1]; 1739 var arrayNotation, funcNotation, o, innerSrc; 1740 1741 for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) { 1742 // Protect against prototype pollution 1743 if (a[i] === '__proto__' || a[i] === 'constructor') { 1744 throw new Error('Cannot set prototype values'); 1745 } 1746 1747 // Check if we are dealing with an array notation request 1748 arrayNotation = a[i].match(__reArray); 1749 funcNotation = a[i].match(__reFn); 1750 1751 if ( arrayNotation ) { 1752 a[i] = a[i].replace(__reArray, ''); 1753 data[ a[i] ] = []; 1754 1755 // Get the remainder of the nested object to set so we can recurse 1756 b = a.slice(); 1757 b.splice( 0, i+1 ); 1758 innerSrc = b.join('.'); 1759 1760 // Traverse each entry in the array setting the properties requested 1761 if ( Array.isArray( val ) ) { 1762 for ( var j=0, jLen=val.length ; j<jLen ; j++ ) { 1763 o = {}; 1764 setData( o, val[j], innerSrc ); 1765 data[ a[i] ].push( o ); 1766 } 1767 } 1768 else { 1769 // We've been asked to save data to an array, but it 1770 // isn't array data to be saved. Best that can be done 1771 // is to just save the value. 1772 data[ a[i] ] = val; 1773 } 1774 1775 // The inner call to setData has already traversed through the remainder 1776 // of the source and has set the data, thus we can exit here 1777 return; 1778 } 1779 else if ( funcNotation ) { 1780 // Function call 1781 a[i] = a[i].replace(__reFn, ''); 1782 data = data[ a[i] ]( val ); 1783 } 1784 1785 // If the nested object doesn't currently exist - since we are 1786 // trying to set the value - create it 1787 if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) { 1788 data[ a[i] ] = {}; 1789 } 1790 data = data[ a[i] ]; 1791 } 1792 1793 // Last item in the input - i.e, the actual set 1794 if ( aLast.match(__reFn ) ) { 1795 // Function call 1796 data = data[ aLast.replace(__reFn, '') ]( val ); 1797 } 1798 else { 1799 // If array notation is used, we just want to strip it and use the property name 1800 // and assign the value. If it isn't used, then we get the result we want anyway 1801 data[ aLast.replace(__reArray, '') ] = val; 1802 } 1803 }; 1804 1805 return function (data, val) { // meta is also passed in, but not used 1806 return setData( data, val, source ); 1807 }; 1808 } 1809 else { 1810 // Array or flat object mapping 1811 return function (data, val) { // meta is also passed in, but not used 1812 data[source] = val; 1813 }; 1814 } 1815 }, 1816 1817 /** 1818 * Create a function that will read nested objects from arrays, based on JSON notation 1819 * @param {*} source JSON notation string 1820 * @returns Value read 1821 */ 1822 get: function ( source ) { 1823 if ( $.isPlainObject( source ) ) { 1824 // Build an object of get functions, and wrap them in a single call 1825 var o = {}; 1826 $.each( source, function (key, val) { 1827 if ( val ) { 1828 o[key] = DataTable.util.get( val ); 1829 } 1830 } ); 1831 1832 return function (data, type, row, meta) { 1833 var t = o[type] || o._; 1834 return t !== undefined ? 1835 t(data, type, row, meta) : 1836 data; 1837 }; 1838 } 1839 else if ( source === null ) { 1840 // Give an empty string for rendering / sorting etc 1841 return function (data) { // type, row and meta also passed, but not used 1842 return data; 1843 }; 1844 } 1845 else if ( typeof source === 'function' ) { 1846 return function (data, type, row, meta) { 1847 return source( data, type, row, meta ); 1848 }; 1849 } 1850 else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || 1851 source.indexOf('[') !== -1 || source.indexOf('(') !== -1) ) 1852 { 1853 /* If there is a . in the source string then the data source is in a 1854 * nested object so we loop over the data for each level to get the next 1855 * level down. On each loop we test for undefined, and if found immediately 1856 * return. This allows entire objects to be missing and sDefaultContent to 1857 * be used if defined, rather than throwing an error 1858 */ 1859 var fetchData = function (data, type, src) { 1860 var arrayNotation, funcNotation, out, innerSrc; 1861 1862 if ( src !== "" ) { 1863 var a = _fnSplitObjNotation( src ); 1864 1865 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { 1866 // Check if we are dealing with special notation 1867 arrayNotation = a[i].match(__reArray); 1868 funcNotation = a[i].match(__reFn); 1869 1870 if ( arrayNotation ) { 1871 // Array notation 1872 a[i] = a[i].replace(__reArray, ''); 1873 1874 // Condition allows simply [] to be passed in 1875 if ( a[i] !== "" ) { 1876 data = data[ a[i] ]; 1877 } 1878 out = []; 1879 1880 // Get the remainder of the nested object to get 1881 a.splice( 0, i+1 ); 1882 innerSrc = a.join('.'); 1883 1884 // Traverse each entry in the array getting the properties requested 1885 if ( Array.isArray( data ) ) { 1886 for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { 1887 out.push( fetchData( data[j], type, innerSrc ) ); 1888 } 1889 } 1890 1891 // If a string is given in between the array notation indicators, that 1892 // is used to join the strings together, otherwise an array is returned 1893 var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); 1894 data = (join==="") ? out : out.join(join); 1895 1896 // The inner call to fetchData has already traversed through the remainder 1897 // of the source requested, so we exit from the loop 1898 break; 1899 } 1900 else if ( funcNotation ) { 1901 // Function call 1902 a[i] = a[i].replace(__reFn, ''); 1903 data = data[ a[i] ](); 1904 continue; 1905 } 1906 1907 if ( data === null || data[ a[i] ] === undefined ) { 1908 return undefined; 1909 } 1910 1911 data = data[ a[i] ]; 1912 } 1913 } 1914 1915 return data; 1916 }; 1917 1918 return function (data, type) { // row and meta also passed, but not used 1919 return fetchData( data, type, source ); 1920 }; 1921 } 1922 else { 1923 // Array or flat object mapping 1924 return function (data, type) { // row and meta also passed, but not used 1925 return data[source]; 1926 }; 1927 } 1928 } 1929 }; 1930 1931 1932 1933 /** 1934 * Create a mapping object that allows camel case parameters to be looked up 1935 * for their Hungarian counterparts. The mapping is stored in a private 1936 * parameter called `_hungarianMap` which can be accessed on the source object. 1937 * @param {object} o 1938 * @memberof DataTable#oApi 1939 */ 1940 function _fnHungarianMap ( o ) 1941 { 1942 var 1943 hungarian = 'a aa ai ao as b fn i m o s ', 1944 match, 1945 newKey, 1946 map = {}; 1947 1948 $.each( o, function (key, val) { 1949 match = key.match(/^([^A-Z]+?)([A-Z])/); 1950 1951 if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) 1952 { 1953 newKey = key.replace( match[0], match[2].toLowerCase() ); 1954 map[ newKey ] = key; 1955 1956 if ( match[1] === 'o' ) 1957 { 1958 _fnHungarianMap( o[key] ); 1959 } 1960 } 1961 } ); 1962 1963 o._hungarianMap = map; 1964 } 1965 1966 1967 /** 1968 * Convert from camel case parameters to Hungarian, based on a Hungarian map 1969 * created by _fnHungarianMap. 1970 * @param {object} src The model object which holds all parameters that can be 1971 * mapped. 1972 * @param {object} user The object to convert from camel case to Hungarian. 1973 * @param {boolean} force When set to `true`, properties which already have a 1974 * Hungarian value in the `user` object will be overwritten. Otherwise they 1975 * won't be. 1976 * @memberof DataTable#oApi 1977 */ 1978 function _fnCamelToHungarian ( src, user, force ) 1979 { 1980 if ( ! src._hungarianMap ) { 1981 _fnHungarianMap( src ); 1982 } 1983 1984 var hungarianKey; 1985 1986 $.each( user, function (key, val) { 1987 hungarianKey = src._hungarianMap[ key ]; 1988 1989 if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) 1990 { 1991 // For objects, we need to buzz down into the object to copy parameters 1992 if ( hungarianKey.charAt(0) === 'o' ) 1993 { 1994 // Copy the camelCase options over to the hungarian 1995 if ( ! user[ hungarianKey ] ) { 1996 user[ hungarianKey ] = {}; 1997 } 1998 $.extend( true, user[hungarianKey], user[key] ); 1999 2000 _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force ); 2001 } 2002 else { 2003 user[hungarianKey] = user[ key ]; 2004 } 2005 } 2006 } ); 2007 } 2008 2009 2010 /** 2011 * Language compatibility - when certain options are given, and others aren't, we 2012 * need to duplicate the values over, in order to provide backwards compatibility 2013 * with older language files. 2014 * @param {object} oSettings dataTables settings object 2015 * @memberof DataTable#oApi 2016 */ 2017 function _fnLanguageCompat( lang ) 2018 { 2019 // Note the use of the Hungarian notation for the parameters in this method as 2020 // this is called after the mapping of camelCase to Hungarian 2021 var defaults = DataTable.defaults.oLanguage; 2022 2023 // Default mapping 2024 var defaultDecimal = defaults.sDecimal; 2025 if ( defaultDecimal ) { 2026 _addNumericSort( defaultDecimal ); 2027 } 2028 2029 if ( lang ) { 2030 var zeroRecords = lang.sZeroRecords; 2031 2032 // Backwards compatibility - if there is no sEmptyTable given, then use the same as 2033 // sZeroRecords - assuming that is given. 2034 if ( ! lang.sEmptyTable && zeroRecords && 2035 defaults.sEmptyTable === "No data available in table" ) 2036 { 2037 _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' ); 2038 } 2039 2040 // Likewise with loading records 2041 if ( ! lang.sLoadingRecords && zeroRecords && 2042 defaults.sLoadingRecords === "Loading..." ) 2043 { 2044 _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' ); 2045 } 2046 2047 // Old parameter name of the thousands separator mapped onto the new 2048 if ( lang.sInfoThousands ) { 2049 lang.sThousands = lang.sInfoThousands; 2050 } 2051 2052 var decimal = lang.sDecimal; 2053 if ( decimal && defaultDecimal !== decimal ) { 2054 _addNumericSort( decimal ); 2055 } 2056 } 2057 } 2058 2059 2060 /** 2061 * Map one parameter onto another 2062 * @param {object} o Object to map 2063 * @param {*} knew The new parameter name 2064 * @param {*} old The old parameter name 2065 */ 2066 var _fnCompatMap = function ( o, knew, old ) { 2067 if ( o[ knew ] !== undefined ) { 2068 o[ old ] = o[ knew ]; 2069 } 2070 }; 2071 2072 2073 /** 2074 * Provide backwards compatibility for the main DT options. Note that the new 2075 * options are mapped onto the old parameters, so this is an external interface 2076 * change only. 2077 * @param {object} init Object to map 2078 */ 2079 function _fnCompatOpts ( init ) 2080 { 2081 _fnCompatMap( init, 'ordering', 'bSort' ); 2082 _fnCompatMap( init, 'orderMulti', 'bSortMulti' ); 2083 _fnCompatMap( init, 'orderClasses', 'bSortClasses' ); 2084 _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' ); 2085 _fnCompatMap( init, 'order', 'aaSorting' ); 2086 _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); 2087 _fnCompatMap( init, 'paging', 'bPaginate' ); 2088 _fnCompatMap( init, 'pagingType', 'sPaginationType' ); 2089 _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); 2090 _fnCompatMap( init, 'searching', 'bFilter' ); 2091 2092 // Boolean initialisation of x-scrolling 2093 if ( typeof init.sScrollX === 'boolean' ) { 2094 init.sScrollX = init.sScrollX ? '100%' : ''; 2095 } 2096 if ( typeof init.scrollX === 'boolean' ) { 2097 init.scrollX = init.scrollX ? '100%' : ''; 2098 } 2099 2100 // Column search objects are in an array, so it needs to be converted 2101 // element by element 2102 var searchCols = init.aoSearchCols; 2103 2104 if ( searchCols ) { 2105 for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { 2106 if ( searchCols[i] ) { 2107 _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); 2108 } 2109 } 2110 } 2111 } 2112 2113 2114 /** 2115 * Provide backwards compatibility for column options. Note that the new options 2116 * are mapped onto the old parameters, so this is an external interface change 2117 * only. 2118 * @param {object} init Object to map 2119 */ 2120 function _fnCompatCols ( init ) 2121 { 2122 _fnCompatMap( init, 'orderable', 'bSortable' ); 2123 _fnCompatMap( init, 'orderData', 'aDataSort' ); 2124 _fnCompatMap( init, 'orderSequence', 'asSorting' ); 2125 _fnCompatMap( init, 'orderDataType', 'sortDataType' ); 2126 2127 // orderData can be given as an integer 2128 var dataSort = init.aDataSort; 2129 if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) { 2130 init.aDataSort = [ dataSort ]; 2131 } 2132 } 2133 2134 2135 /** 2136 * Browser feature detection for capabilities, quirks 2137 * @param {object} settings dataTables settings object 2138 * @memberof DataTable#oApi 2139 */ 2140 function _fnBrowserDetect( settings ) 2141 { 2142 // We don't need to do this every time DataTables is constructed, the values 2143 // calculated are specific to the browser and OS configuration which we 2144 // don't expect to change between initialisations 2145 if ( ! DataTable.__browser ) { 2146 var browser = {}; 2147 DataTable.__browser = browser; 2148 2149 // Scrolling feature / quirks detection 2150 var n = $('<div/>') 2151 .css( { 2152 position: 'fixed', 2153 top: 0, 2154 left: $(window).scrollLeft()*-1, // allow for scrolling 2155 height: 1, 2156 width: 1, 2157 overflow: 'hidden' 2158 } ) 2159 .append( 2160 $('<div/>') 2161 .css( { 2162 position: 'absolute', 2163 top: 1, 2164 left: 1, 2165 width: 100, 2166 overflow: 'scroll' 2167 } ) 2168 .append( 2169 $('<div/>') 2170 .css( { 2171 width: '100%', 2172 height: 10 2173 } ) 2174 ) 2175 ) 2176 .appendTo( 'body' ); 2177 2178 var outer = n.children(); 2179 var inner = outer.children(); 2180 2181 // Numbers below, in order, are: 2182 // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth 2183 // 2184 // IE6 XP: 100 100 100 83 2185 // IE7 Vista: 100 100 100 83 2186 // IE 8+ Windows: 83 83 100 83 2187 // Evergreen Windows: 83 83 100 83 2188 // Evergreen Mac with scrollbars: 85 85 100 85 2189 // Evergreen Mac without scrollbars: 100 100 100 100 2190 2191 // Get scrollbar width 2192 browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; 2193 2194 // IE6/7 will oversize a width 100% element inside a scrolling element, to 2195 // include the width of the scrollbar, while other browsers ensure the inner 2196 // element is contained without forcing scrolling 2197 browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; 2198 2199 // In rtl text layout, some browsers (most, but not all) will place the 2200 // scrollbar on the left, rather than the right. 2201 browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; 2202 2203 // IE8- don't provide height and width for getBoundingClientRect 2204 browser.bBounding = n[0].getBoundingClientRect().width ? true : false; 2205 2206 n.remove(); 2207 } 2208 2209 $.extend( settings.oBrowser, DataTable.__browser ); 2210 settings.oScroll.iBarWidth = DataTable.__browser.barWidth; 2211 } 2212 2213 2214 /** 2215 * Array.prototype reduce[Right] method, used for browsers which don't support 2216 * JS 1.6. Done this way to reduce code size, since we iterate either way 2217 * @param {object} settings dataTables settings object 2218 * @memberof DataTable#oApi 2219 */ 2220 function _fnReduce ( that, fn, init, start, end, inc ) 2221 { 2222 var 2223 i = start, 2224 value, 2225 isSet = false; 2226 2227 if ( init !== undefined ) { 2228 value = init; 2229 isSet = true; 2230 } 2231 2232 while ( i !== end ) { 2233 if ( ! that.hasOwnProperty(i) ) { 2234 continue; 2235 } 2236 2237 value = isSet ? 2238 fn( value, that[i], i, that ) : 2239 that[i]; 2240 2241 isSet = true; 2242 i += inc; 2243 } 2244 2245 return value; 2246 } 2247 2248 /** 2249 * Add a column to the list used for the table with default values 2250 * @param {object} oSettings dataTables settings object 2251 * @param {node} nTh The th element for this column 2252 * @memberof DataTable#oApi 2253 */ 2254 function _fnAddColumn( oSettings, nTh ) 2255 { 2256 // Add column to aoColumns array 2257 var oDefaults = DataTable.defaults.column; 2258 var iCol = oSettings.aoColumns.length; 2259 var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { 2260 "nTh": nTh ? nTh : document.createElement('th'), 2261 "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', 2262 "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], 2263 "mData": oDefaults.mData ? oDefaults.mData : iCol, 2264 idx: iCol 2265 } ); 2266 oSettings.aoColumns.push( oCol ); 2267 2268 // Add search object for column specific search. Note that the `searchCols[ iCol ]` 2269 // passed into extend can be undefined. This allows the user to give a default 2270 // with only some of the parameters defined, and also not give a default 2271 var searchCols = oSettings.aoPreSearchCols; 2272 searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); 2273 2274 // Use the default column options function to initialise classes etc 2275 _fnColumnOptions( oSettings, iCol, $(nTh).data() ); 2276 } 2277 2278 2279 /** 2280 * Apply options for a column 2281 * @param {object} oSettings dataTables settings object 2282 * @param {int} iCol column index to consider 2283 * @param {object} oOptions object with sType, bVisible and bSearchable etc 2284 * @memberof DataTable#oApi 2285 */ 2286 function _fnColumnOptions( oSettings, iCol, oOptions ) 2287 { 2288 var oCol = oSettings.aoColumns[ iCol ]; 2289 var oClasses = oSettings.oClasses; 2290 var th = $(oCol.nTh); 2291 2292 // Try to get width information from the DOM. We can't get it from CSS 2293 // as we'd need to parse the CSS stylesheet. `width` option can override 2294 if ( ! oCol.sWidthOrig ) { 2295 // Width attribute 2296 oCol.sWidthOrig = th.attr('width') || null; 2297 2298 // Style attribute 2299 var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); 2300 if ( t ) { 2301 oCol.sWidthOrig = t[1]; 2302 } 2303 } 2304 2305 /* User specified column options */ 2306 if ( oOptions !== undefined && oOptions !== null ) 2307 { 2308 // Backwards compatibility 2309 _fnCompatCols( oOptions ); 2310 2311 // Map camel case parameters to their Hungarian counterparts 2312 _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); 2313 2314 /* Backwards compatibility for mDataProp */ 2315 if ( oOptions.mDataProp !== undefined && !oOptions.mData ) 2316 { 2317 oOptions.mData = oOptions.mDataProp; 2318 } 2319 2320 if ( oOptions.sType ) 2321 { 2322 oCol._sManualType = oOptions.sType; 2323 } 2324 2325 // `class` is a reserved word in Javascript, so we need to provide 2326 // the ability to use a valid name for the camel case input 2327 if ( oOptions.className && ! oOptions.sClass ) 2328 { 2329 oOptions.sClass = oOptions.className; 2330 } 2331 if ( oOptions.sClass ) { 2332 th.addClass( oOptions.sClass ); 2333 } 2334 2335 var origClass = oCol.sClass; 2336 2337 $.extend( oCol, oOptions ); 2338 _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); 2339 2340 // Merge class from previously defined classes with this one, rather than just 2341 // overwriting it in the extend above 2342 if (origClass !== oCol.sClass) { 2343 oCol.sClass = origClass + ' ' + oCol.sClass; 2344 } 2345 2346 /* iDataSort to be applied (backwards compatibility), but aDataSort will take 2347 * priority if defined 2348 */ 2349 if ( oOptions.iDataSort !== undefined ) 2350 { 2351 oCol.aDataSort = [ oOptions.iDataSort ]; 2352 } 2353 _fnMap( oCol, oOptions, "aDataSort" ); 2354 } 2355 2356 /* Cache the data get and set functions for speed */ 2357 var mDataSrc = oCol.mData; 2358 var mData = _fnGetObjectDataFn( mDataSrc ); 2359 var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; 2360 2361 var attrTest = function( src ) { 2362 return typeof src === 'string' && src.indexOf('@') !== -1; 2363 }; 2364 oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( 2365 attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) 2366 ); 2367 oCol._setter = null; 2368 2369 oCol.fnGetData = function (rowData, type, meta) { 2370 var innerData = mData( rowData, type, undefined, meta ); 2371 2372 return mRender && type ? 2373 mRender( innerData, type, rowData, meta ) : 2374 innerData; 2375 }; 2376 oCol.fnSetData = function ( rowData, val, meta ) { 2377 return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); 2378 }; 2379 2380 // Indicate if DataTables should read DOM data as an object or array 2381 // Used in _fnGetRowElements 2382 if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) { 2383 oSettings._rowReadObject = true; 2384 } 2385 2386 /* Feature sorting overrides column specific when off */ 2387 if ( !oSettings.oFeatures.bSort ) 2388 { 2389 oCol.bSortable = false; 2390 th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called 2391 } 2392 2393 /* Check that the class assignment is correct for sorting */ 2394 var bAsc = $.inArray('asc', oCol.asSorting) !== -1; 2395 var bDesc = $.inArray('desc', oCol.asSorting) !== -1; 2396 if ( !oCol.bSortable || (!bAsc && !bDesc) ) 2397 { 2398 oCol.sSortingClass = oClasses.sSortableNone; 2399 oCol.sSortingClassJUI = ""; 2400 } 2401 else if ( bAsc && !bDesc ) 2402 { 2403 oCol.sSortingClass = oClasses.sSortableAsc; 2404 oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; 2405 } 2406 else if ( !bAsc && bDesc ) 2407 { 2408 oCol.sSortingClass = oClasses.sSortableDesc; 2409 oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; 2410 } 2411 else 2412 { 2413 oCol.sSortingClass = oClasses.sSortable; 2414 oCol.sSortingClassJUI = oClasses.sSortJUI; 2415 } 2416 } 2417 2418 2419 /** 2420 * Adjust the table column widths for new data. Note: you would probably want to 2421 * do a redraw after calling this function! 2422 * @param {object} settings dataTables settings object 2423 * @memberof DataTable#oApi 2424 */ 2425 function _fnAdjustColumnSizing ( settings ) 2426 { 2427 /* Not interested in doing column width calculation if auto-width is disabled */ 2428 if ( settings.oFeatures.bAutoWidth !== false ) 2429 { 2430 var columns = settings.aoColumns; 2431 2432 _fnCalculateColumnWidths( settings ); 2433 for ( var i=0 , iLen=columns.length ; i<iLen ; i++ ) 2434 { 2435 columns[i].nTh.style.width = columns[i].sWidth; 2436 } 2437 } 2438 2439 var scroll = settings.oScroll; 2440 if ( scroll.sY !== '' || scroll.sX !== '') 2441 { 2442 _fnScrollDraw( settings ); 2443 } 2444 2445 _fnCallbackFire( settings, null, 'column-sizing', [settings] ); 2446 } 2447 2448 2449 /** 2450 * Convert the index of a visible column to the index in the data array (take account 2451 * of hidden columns) 2452 * @param {object} oSettings dataTables settings object 2453 * @param {int} iMatch Visible column index to lookup 2454 * @returns {int} i the data index 2455 * @memberof DataTable#oApi 2456 */ 2457 function _fnVisibleToColumnIndex( oSettings, iMatch ) 2458 { 2459 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2460 2461 return typeof aiVis[iMatch] === 'number' ? 2462 aiVis[iMatch] : 2463 null; 2464 } 2465 2466 2467 /** 2468 * Convert the index of an index in the data array and convert it to the visible 2469 * column index (take account of hidden columns) 2470 * @param {int} iMatch Column index to lookup 2471 * @param {object} oSettings dataTables settings object 2472 * @returns {int} i the data index 2473 * @memberof DataTable#oApi 2474 */ 2475 function _fnColumnIndexToVisible( oSettings, iMatch ) 2476 { 2477 var aiVis = _fnGetColumns( oSettings, 'bVisible' ); 2478 var iPos = $.inArray( iMatch, aiVis ); 2479 2480 return iPos !== -1 ? iPos : null; 2481 } 2482 2483 2484 /** 2485 * Get the number of visible columns 2486 * @param {object} oSettings dataTables settings object 2487 * @returns {int} i the number of visible columns 2488 * @memberof DataTable#oApi 2489 */ 2490 function _fnVisbleColumns( oSettings ) 2491 { 2492 var vis = 0; 2493 2494 // No reduce in IE8, use a loop for now 2495 $.each( oSettings.aoColumns, function ( i, col ) { 2496 if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) { 2497 vis++; 2498 } 2499 } ); 2500 2501 return vis; 2502 } 2503 2504 2505 /** 2506 * Get an array of column indexes that match a given property 2507 * @param {object} oSettings dataTables settings object 2508 * @param {string} sParam Parameter in aoColumns to look for - typically 2509 * bVisible or bSearchable 2510 * @returns {array} Array of indexes with matched properties 2511 * @memberof DataTable#oApi 2512 */ 2513 function _fnGetColumns( oSettings, sParam ) 2514 { 2515 var a = []; 2516 2517 $.map( oSettings.aoColumns, function(val, i) { 2518 if ( val[sParam] ) { 2519 a.push( i ); 2520 } 2521 } ); 2522 2523 return a; 2524 } 2525 2526 2527 /** 2528 * Calculate the 'type' of a column 2529 * @param {object} settings dataTables settings object 2530 * @memberof DataTable#oApi 2531 */ 2532 function _fnColumnTypes ( settings ) 2533 { 2534 var columns = settings.aoColumns; 2535 var data = settings.aoData; 2536 var types = DataTable.ext.type.detect; 2537 var i, ien, j, jen, k, ken; 2538 var col, cell, detectedType, cache; 2539 2540 // For each column, spin over the 2541 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 2542 col = columns[i]; 2543 cache = []; 2544 2545 if ( ! col.sType && col._sManualType ) { 2546 col.sType = col._sManualType; 2547 } 2548 else if ( ! col.sType ) { 2549 for ( j=0, jen=types.length ; j<jen ; j++ ) { 2550 for ( k=0, ken=data.length ; k<ken ; k++ ) { 2551 // Use a cache array so we only need to get the type data 2552 // from the formatter once (when using multiple detectors) 2553 if ( cache[k] === undefined ) { 2554 cache[k] = _fnGetCellData( settings, k, i, 'type' ); 2555 } 2556 2557 detectedType = types[j]( cache[k], settings ); 2558 2559 // If null, then this type can't apply to this column, so 2560 // rather than testing all cells, break out. There is an 2561 // exception for the last type which is `html`. We need to 2562 // scan all rows since it is possible to mix string and HTML 2563 // types 2564 if ( ! detectedType && j !== types.length-1 ) { 2565 break; 2566 } 2567 2568 // Only a single match is needed for html type since it is 2569 // bottom of the pile and very similar to string - but it 2570 // must not be empty 2571 if ( detectedType === 'html' && ! _empty(cache[k]) ) { 2572 break; 2573 } 2574 } 2575 2576 // Type is valid for all data points in the column - use this 2577 // type 2578 if ( detectedType ) { 2579 col.sType = detectedType; 2580 break; 2581 } 2582 } 2583 2584 // Fall back - if no type was detected, always use string 2585 if ( ! col.sType ) { 2586 col.sType = 'string'; 2587 } 2588 } 2589 } 2590 } 2591 2592 2593 /** 2594 * Take the column definitions and static columns arrays and calculate how 2595 * they relate to column indexes. The callback function will then apply the 2596 * definition found for a column to a suitable configuration object. 2597 * @param {object} oSettings dataTables settings object 2598 * @param {array} aoColDefs The aoColumnDefs array that is to be applied 2599 * @param {array} aoCols The aoColumns array that defines columns individually 2600 * @param {function} fn Callback function - takes two parameters, the calculated 2601 * column index and the definition for that column. 2602 * @memberof DataTable#oApi 2603 */ 2604 function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn ) 2605 { 2606 var i, iLen, j, jLen, k, kLen, def; 2607 var columns = oSettings.aoColumns; 2608 2609 // Column definitions with aTargets 2610 if ( aoColDefs ) 2611 { 2612 /* Loop over the definitions array - loop in reverse so first instance has priority */ 2613 for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) 2614 { 2615 def = aoColDefs[i]; 2616 2617 /* Each definition can target multiple columns, as it is an array */ 2618 var aTargets = def.target !== undefined 2619 ? def.target 2620 : def.targets !== undefined 2621 ? def.targets 2622 : def.aTargets; 2623 2624 if ( ! Array.isArray( aTargets ) ) 2625 { 2626 aTargets = [ aTargets ]; 2627 } 2628 2629 for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) 2630 { 2631 if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 ) 2632 { 2633 /* Add columns that we don't yet know about */ 2634 while( columns.length <= aTargets[j] ) 2635 { 2636 _fnAddColumn( oSettings ); 2637 } 2638 2639 /* Integer, basic index */ 2640 fn( aTargets[j], def ); 2641 } 2642 else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 ) 2643 { 2644 /* Negative integer, right to left column counting */ 2645 fn( columns.length+aTargets[j], def ); 2646 } 2647 else if ( typeof aTargets[j] === 'string' ) 2648 { 2649 /* Class name matching on TH element */ 2650 for ( k=0, kLen=columns.length ; k<kLen ; k++ ) 2651 { 2652 if ( aTargets[j] == "_all" || 2653 $(columns[k].nTh).hasClass( aTargets[j] ) ) 2654 { 2655 fn( k, def ); 2656 } 2657 } 2658 } 2659 } 2660 } 2661 } 2662 2663 // Statically defined columns array 2664 if ( aoCols ) 2665 { 2666 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) 2667 { 2668 fn( i, aoCols[i] ); 2669 } 2670 } 2671 } 2672 2673 /** 2674 * Add a data array to the table, creating DOM node etc. This is the parallel to 2675 * _fnGatherData, but for adding rows from a Javascript source, rather than a 2676 * DOM source. 2677 * @param {object} oSettings dataTables settings object 2678 * @param {array} aData data array to be added 2679 * @param {node} [nTr] TR element to add to the table - optional. If not given, 2680 * DataTables will create a row automatically 2681 * @param {array} [anTds] Array of TD|TH elements for the row - must be given 2682 * if nTr is. 2683 * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed 2684 * @memberof DataTable#oApi 2685 */ 2686 function _fnAddData ( oSettings, aDataIn, nTr, anTds ) 2687 { 2688 /* Create the object for storing information about this new row */ 2689 var iRow = oSettings.aoData.length; 2690 var oData = $.extend( true, {}, DataTable.models.oRow, { 2691 src: nTr ? 'dom' : 'data', 2692 idx: iRow 2693 } ); 2694 2695 oData._aData = aDataIn; 2696 oSettings.aoData.push( oData ); 2697 2698 /* Create the cells */ 2699 var nTd, sThisType; 2700 var columns = oSettings.aoColumns; 2701 2702 // Invalidate the column types as the new data needs to be revalidated 2703 for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) 2704 { 2705 columns[i].sType = null; 2706 } 2707 2708 /* Add to the display array */ 2709 oSettings.aiDisplayMaster.push( iRow ); 2710 2711 var id = oSettings.rowIdFn( aDataIn ); 2712 if ( id !== undefined ) { 2713 oSettings.aIds[ id ] = oData; 2714 } 2715 2716 /* Create the DOM information, or register it if already present */ 2717 if ( nTr || ! oSettings.oFeatures.bDeferRender ) 2718 { 2719 _fnCreateTr( oSettings, iRow, nTr, anTds ); 2720 } 2721 2722 return iRow; 2723 } 2724 2725 2726 /** 2727 * Add one or more TR elements to the table. Generally we'd expect to 2728 * use this for reading data from a DOM sourced table, but it could be 2729 * used for an TR element. Note that if a TR is given, it is used (i.e. 2730 * it is not cloned). 2731 * @param {object} settings dataTables settings object 2732 * @param {array|node|jQuery} trs The TR element(s) to add to the table 2733 * @returns {array} Array of indexes for the added rows 2734 * @memberof DataTable#oApi 2735 */ 2736 function _fnAddTr( settings, trs ) 2737 { 2738 var row; 2739 2740 // Allow an individual node to be passed in 2741 if ( ! (trs instanceof $) ) { 2742 trs = $(trs); 2743 } 2744 2745 return trs.map( function (i, el) { 2746 row = _fnGetRowElements( settings, el ); 2747 return _fnAddData( settings, row.data, el, row.cells ); 2748 } ); 2749 } 2750 2751 2752 /** 2753 * Take a TR element and convert it to an index in aoData 2754 * @param {object} oSettings dataTables settings object 2755 * @param {node} n the TR element to find 2756 * @returns {int} index if the node is found, null if not 2757 * @memberof DataTable#oApi 2758 */ 2759 function _fnNodeToDataIndex( oSettings, n ) 2760 { 2761 return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null; 2762 } 2763 2764 2765 /** 2766 * Take a TD element and convert it into a column data index (not the visible index) 2767 * @param {object} oSettings dataTables settings object 2768 * @param {int} iRow The row number the TD/TH can be found in 2769 * @param {node} n The TD/TH element to find 2770 * @returns {int} index if the node is found, -1 if not 2771 * @memberof DataTable#oApi 2772 */ 2773 function _fnNodeToColumnIndex( oSettings, iRow, n ) 2774 { 2775 return $.inArray( n, oSettings.aoData[ iRow ].anCells ); 2776 } 2777 2778 2779 /** 2780 * Get the data for a given cell from the internal cache, taking into account data mapping 2781 * @param {object} settings dataTables settings object 2782 * @param {int} rowIdx aoData row id 2783 * @param {int} colIdx Column index 2784 * @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order') 2785 * @returns {*} Cell data 2786 * @memberof DataTable#oApi 2787 */ 2788 function _fnGetCellData( settings, rowIdx, colIdx, type ) 2789 { 2790 if (type === 'search') { 2791 type = 'filter'; 2792 } 2793 else if (type === 'order') { 2794 type = 'sort'; 2795 } 2796 2797 var draw = settings.iDraw; 2798 var col = settings.aoColumns[colIdx]; 2799 var rowData = settings.aoData[rowIdx]._aData; 2800 var defaultContent = col.sDefaultContent; 2801 var cellData = col.fnGetData( rowData, type, { 2802 settings: settings, 2803 row: rowIdx, 2804 col: colIdx 2805 } ); 2806 2807 if ( cellData === undefined ) { 2808 if ( settings.iDrawError != draw && defaultContent === null ) { 2809 _fnLog( settings, 0, "Requested unknown parameter "+ 2810 (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ 2811 " for row "+rowIdx+", column "+colIdx, 4 ); 2812 settings.iDrawError = draw; 2813 } 2814 return defaultContent; 2815 } 2816 2817 // When the data source is null and a specific data type is requested (i.e. 2818 // not the original data), we can use default column data 2819 if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { 2820 cellData = defaultContent; 2821 } 2822 else if ( typeof cellData === 'function' ) { 2823 // If the data source is a function, then we run it and use the return, 2824 // executing in the scope of the data object (for instances) 2825 return cellData.call( rowData ); 2826 } 2827 2828 if ( cellData === null && type === 'display' ) { 2829 return ''; 2830 } 2831 2832 if ( type === 'filter' ) { 2833 var fomatters = DataTable.ext.type.search; 2834 2835 if ( fomatters[ col.sType ] ) { 2836 cellData = fomatters[ col.sType ]( cellData ); 2837 } 2838 } 2839 2840 return cellData; 2841 } 2842 2843 2844 /** 2845 * Set the value for a specific cell, into the internal data cache 2846 * @param {object} settings dataTables settings object 2847 * @param {int} rowIdx aoData row id 2848 * @param {int} colIdx Column index 2849 * @param {*} val Value to set 2850 * @memberof DataTable#oApi 2851 */ 2852 function _fnSetCellData( settings, rowIdx, colIdx, val ) 2853 { 2854 var col = settings.aoColumns[colIdx]; 2855 var rowData = settings.aoData[rowIdx]._aData; 2856 2857 col.fnSetData( rowData, val, { 2858 settings: settings, 2859 row: rowIdx, 2860 col: colIdx 2861 } ); 2862 } 2863 2864 2865 // Private variable that is used to match action syntax in the data property object 2866 var __reArray = /\[.*?\]$/; 2867 var __reFn = /\(\)$/; 2868 2869 /** 2870 * Split string on periods, taking into account escaped periods 2871 * @param {string} str String to split 2872 * @return {array} Split string 2873 */ 2874 function _fnSplitObjNotation( str ) 2875 { 2876 return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) { 2877 return s.replace(/\\\./g, '.'); 2878 } ); 2879 } 2880 2881 2882 /** 2883 * Return a function that can be used to get data from a source object, taking 2884 * into account the ability to use nested objects as a source 2885 * @param {string|int|function} mSource The data source for the object 2886 * @returns {function} Data get function 2887 * @memberof DataTable#oApi 2888 */ 2889 var _fnGetObjectDataFn = DataTable.util.get; 2890 2891 2892 /** 2893 * Return a function that can be used to set data from a source object, taking 2894 * into account the ability to use nested objects as a source 2895 * @param {string|int|function} mSource The data source for the object 2896 * @returns {function} Data set function 2897 * @memberof DataTable#oApi 2898 */ 2899 var _fnSetObjectDataFn = DataTable.util.set; 2900 2901 2902 /** 2903 * Return an array with the full table data 2904 * @param {object} oSettings dataTables settings object 2905 * @returns array {array} aData Master data array 2906 * @memberof DataTable#oApi 2907 */ 2908 function _fnGetDataMaster ( settings ) 2909 { 2910 return _pluck( settings.aoData, '_aData' ); 2911 } 2912 2913 2914 /** 2915 * Nuke the table 2916 * @param {object} oSettings dataTables settings object 2917 * @memberof DataTable#oApi 2918 */ 2919 function _fnClearTable( settings ) 2920 { 2921 settings.aoData.length = 0; 2922 settings.aiDisplayMaster.length = 0; 2923 settings.aiDisplay.length = 0; 2924 settings.aIds = {}; 2925 } 2926 2927 2928 /** 2929 * Take an array of integers (index array) and remove a target integer (value - not 2930 * the key!) 2931 * @param {array} a Index array to target 2932 * @param {int} iTarget value to find 2933 * @memberof DataTable#oApi 2934 */ 2935 function _fnDeleteIndex( a, iTarget, splice ) 2936 { 2937 var iTargetIndex = -1; 2938 2939 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) 2940 { 2941 if ( a[i] == iTarget ) 2942 { 2943 iTargetIndex = i; 2944 } 2945 else if ( a[i] > iTarget ) 2946 { 2947 a[i]--; 2948 } 2949 } 2950 2951 if ( iTargetIndex != -1 && splice === undefined ) 2952 { 2953 a.splice( iTargetIndex, 1 ); 2954 } 2955 } 2956 2957 2958 /** 2959 * Mark cached data as invalid such that a re-read of the data will occur when 2960 * the cached data is next requested. Also update from the data source object. 2961 * 2962 * @param {object} settings DataTables settings object 2963 * @param {int} rowIdx Row index to invalidate 2964 * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' 2965 * or 'data' 2966 * @param {int} [colIdx] Column index to invalidate. If undefined the whole 2967 * row will be invalidated 2968 * @memberof DataTable#oApi 2969 * 2970 * @todo For the modularisation of v1.11 this will need to become a callback, so 2971 * the sort and filter methods can subscribe to it. That will required 2972 * initialisation options for sorting, which is why it is not already baked in 2973 */ 2974 function _fnInvalidate( settings, rowIdx, src, colIdx ) 2975 { 2976 var row = settings.aoData[ rowIdx ]; 2977 var i, ien; 2978 var cellWrite = function ( cell, col ) { 2979 // This is very frustrating, but in IE if you just write directly 2980 // to innerHTML, and elements that are overwritten are GC'ed, 2981 // even if there is a reference to them elsewhere 2982 while ( cell.childNodes.length ) { 2983 cell.removeChild( cell.firstChild ); 2984 } 2985 2986 cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' ); 2987 }; 2988 2989 // Are we reading last data from DOM or the data object? 2990 if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { 2991 // Read the data from the DOM 2992 row._aData = _fnGetRowElements( 2993 settings, row, colIdx, colIdx === undefined ? undefined : row._aData 2994 ) 2995 .data; 2996 } 2997 else { 2998 // Reading from data object, update the DOM 2999 var cells = row.anCells; 3000 3001 if ( cells ) { 3002 if ( colIdx !== undefined ) { 3003 cellWrite( cells[colIdx], colIdx ); 3004 } 3005 else { 3006 for ( i=0, ien=cells.length ; i<ien ; i++ ) { 3007 cellWrite( cells[i], i ); 3008 } 3009 } 3010 } 3011 } 3012 3013 // For both row and cell invalidation, the cached data for sorting and 3014 // filtering is nulled out 3015 row._aSortData = null; 3016 row._aFilterData = null; 3017 3018 // Invalidate the type for a specific column (if given) or all columns since 3019 // the data might have changed 3020 var cols = settings.aoColumns; 3021 if ( colIdx !== undefined ) { 3022 cols[ colIdx ].sType = null; 3023 } 3024 else { 3025 for ( i=0, ien=cols.length ; i<ien ; i++ ) { 3026 cols[i].sType = null; 3027 } 3028 3029 // Update DataTables special `DT_*` attributes for the row 3030 _fnRowAttributes( settings, row ); 3031 } 3032 } 3033 3034 3035 /** 3036 * Build a data source object from an HTML row, reading the contents of the 3037 * cells that are in the row. 3038 * 3039 * @param {object} settings DataTables settings object 3040 * @param {node|object} TR element from which to read data or existing row 3041 * object from which to re-read the data from the cells 3042 * @param {int} [colIdx] Optional column index 3043 * @param {array|object} [d] Data source object. If `colIdx` is given then this 3044 * parameter should also be given and will be used to write the data into. 3045 * Only the column in question will be written 3046 * @returns {object} Object with two parameters: `data` the data read, in 3047 * document order, and `cells` and array of nodes (they can be useful to the 3048 * caller, so rather than needing a second traversal to get them, just return 3049 * them from here). 3050 * @memberof DataTable#oApi 3051 */ 3052 function _fnGetRowElements( settings, row, colIdx, d ) 3053 { 3054 var 3055 tds = [], 3056 td = row.firstChild, 3057 name, col, o, i=0, contents, 3058 columns = settings.aoColumns, 3059 objectRead = settings._rowReadObject; 3060 3061 // Allow the data object to be passed in, or construct 3062 d = d !== undefined ? 3063 d : 3064 objectRead ? 3065 {} : 3066 []; 3067 3068 var attr = function ( str, td ) { 3069 if ( typeof str === 'string' ) { 3070 var idx = str.indexOf('@'); 3071 3072 if ( idx !== -1 ) { 3073 var attr = str.substring( idx+1 ); 3074 var setter = _fnSetObjectDataFn( str ); 3075 setter( d, td.getAttribute( attr ) ); 3076 } 3077 } 3078 }; 3079 3080 // Read data from a cell and store into the data object 3081 var cellProcess = function ( cell ) { 3082 if ( colIdx === undefined || colIdx === i ) { 3083 col = columns[i]; 3084 contents = (cell.innerHTML).trim(); 3085 3086 if ( col && col._bAttrSrc ) { 3087 var setter = _fnSetObjectDataFn( col.mData._ ); 3088 setter( d, contents ); 3089 3090 attr( col.mData.sort, cell ); 3091 attr( col.mData.type, cell ); 3092 attr( col.mData.filter, cell ); 3093 } 3094 else { 3095 // Depending on the `data` option for the columns the data can 3096 // be read to either an object or an array. 3097 if ( objectRead ) { 3098 if ( ! col._setter ) { 3099 // Cache the setter function 3100 col._setter = _fnSetObjectDataFn( col.mData ); 3101 } 3102 col._setter( d, contents ); 3103 } 3104 else { 3105 d[i] = contents; 3106 } 3107 } 3108 } 3109 3110 i++; 3111 }; 3112 3113 if ( td ) { 3114 // `tr` element was passed in 3115 while ( td ) { 3116 name = td.nodeName.toUpperCase(); 3117 3118 if ( name == "TD" || name == "TH" ) { 3119 cellProcess( td ); 3120 tds.push( td ); 3121 } 3122 3123 td = td.nextSibling; 3124 } 3125 } 3126 else { 3127 // Existing row object passed in 3128 tds = row.anCells; 3129 3130 for ( var j=0, jen=tds.length ; j<jen ; j++ ) { 3131 cellProcess( tds[j] ); 3132 } 3133 } 3134 3135 // Read the ID from the DOM if present 3136 var rowNode = row.firstChild ? row : row.nTr; 3137 3138 if ( rowNode ) { 3139 var id = rowNode.getAttribute( 'id' ); 3140 3141 if ( id ) { 3142 _fnSetObjectDataFn( settings.rowId )( d, id ); 3143 } 3144 } 3145 3146 return { 3147 data: d, 3148 cells: tds 3149 }; 3150 } 3151 /** 3152 * Create a new TR element (and it's TD children) for a row 3153 * @param {object} oSettings dataTables settings object 3154 * @param {int} iRow Row to consider 3155 * @param {node} [nTrIn] TR element to add to the table - optional. If not given, 3156 * DataTables will create a row automatically 3157 * @param {array} [anTds] Array of TD|TH elements for the row - must be given 3158 * if nTr is. 3159 * @memberof DataTable#oApi 3160 */ 3161 function _fnCreateTr ( oSettings, iRow, nTrIn, anTds ) 3162 { 3163 var 3164 row = oSettings.aoData[iRow], 3165 rowData = row._aData, 3166 cells = [], 3167 nTr, nTd, oCol, 3168 i, iLen, create; 3169 3170 if ( row.nTr === null ) 3171 { 3172 nTr = nTrIn || document.createElement('tr'); 3173 3174 row.nTr = nTr; 3175 row.anCells = cells; 3176 3177 /* Use a private property on the node to allow reserve mapping from the node 3178 * to the aoData array for fast look up 3179 */ 3180 nTr._DT_RowIndex = iRow; 3181 3182 /* Special parameters can be given by the data source to be used on the row */ 3183 _fnRowAttributes( oSettings, row ); 3184 3185 /* Process each column */ 3186 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) 3187 { 3188 oCol = oSettings.aoColumns[i]; 3189 create = nTrIn ? false : true; 3190 3191 nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; 3192 3193 if (! nTd) { 3194 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 3195 } 3196 3197 nTd._DT_CellIndex = { 3198 row: iRow, 3199 column: i 3200 }; 3201 3202 cells.push( nTd ); 3203 3204 // Need to create the HTML if new, or if a rendering function is defined 3205 if ( create || ((oCol.mRender || oCol.mData !== i) && 3206 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display') 3207 )) { 3208 nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' ); 3209 } 3210 3211 /* Add user defined class */ 3212 if ( oCol.sClass ) 3213 { 3214 nTd.className += ' '+oCol.sClass; 3215 } 3216 3217 // Visibility - add or remove as required 3218 if ( oCol.bVisible && ! nTrIn ) 3219 { 3220 nTr.appendChild( nTd ); 3221 } 3222 else if ( ! oCol.bVisible && nTrIn ) 3223 { 3224 nTd.parentNode.removeChild( nTd ); 3225 } 3226 3227 if ( oCol.fnCreatedCell ) 3228 { 3229 oCol.fnCreatedCell.call( oSettings.oInstance, 3230 nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i 3231 ); 3232 } 3233 } 3234 3235 _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow, cells] ); 3236 } 3237 } 3238 3239 3240 /** 3241 * Add attributes to a row based on the special `DT_*` parameters in a data 3242 * source object. 3243 * @param {object} settings DataTables settings object 3244 * @param {object} DataTables row object for the row to be modified 3245 * @memberof DataTable#oApi 3246 */ 3247 function _fnRowAttributes( settings, row ) 3248 { 3249 var tr = row.nTr; 3250 var data = row._aData; 3251 3252 if ( tr ) { 3253 var id = settings.rowIdFn( data ); 3254 3255 if ( id ) { 3256 tr.id = id; 3257 } 3258 3259 if ( data.DT_RowClass ) { 3260 // Remove any classes added by DT_RowClass before 3261 var a = data.DT_RowClass.split(' '); 3262 row.__rowc = row.__rowc ? 3263 _unique( row.__rowc.concat( a ) ) : 3264 a; 3265 3266 $(tr) 3267 .removeClass( row.__rowc.join(' ') ) 3268 .addClass( data.DT_RowClass ); 3269 } 3270 3271 if ( data.DT_RowAttr ) { 3272 $(tr).attr( data.DT_RowAttr ); 3273 } 3274 3275 if ( data.DT_RowData ) { 3276 $(tr).data( data.DT_RowData ); 3277 } 3278 } 3279 } 3280 3281 3282 /** 3283 * Create the HTML header for the table 3284 * @param {object} oSettings dataTables settings object 3285 * @memberof DataTable#oApi 3286 */ 3287 function _fnBuildHead( oSettings ) 3288 { 3289 var i, ien, cell, row, column; 3290 var thead = oSettings.nTHead; 3291 var tfoot = oSettings.nTFoot; 3292 var createHeader = $('th, td', thead).length === 0; 3293 var classes = oSettings.oClasses; 3294 var columns = oSettings.aoColumns; 3295 3296 if ( createHeader ) { 3297 row = $('<tr/>').appendTo( thead ); 3298 } 3299 3300 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 3301 column = columns[i]; 3302 cell = $( column.nTh ).addClass( column.sClass ); 3303 3304 if ( createHeader ) { 3305 cell.appendTo( row ); 3306 } 3307 3308 // 1.11 move into sorting 3309 if ( oSettings.oFeatures.bSort ) { 3310 cell.addClass( column.sSortingClass ); 3311 3312 if ( column.bSortable !== false ) { 3313 cell 3314 .attr( 'tabindex', oSettings.iTabIndex ) 3315 .attr( 'aria-controls', oSettings.sTableId ); 3316 3317 _fnSortAttachListener( oSettings, column.nTh, i ); 3318 } 3319 } 3320 3321 if ( column.sTitle != cell[0].innerHTML ) { 3322 cell.html( column.sTitle ); 3323 } 3324 3325 _fnRenderer( oSettings, 'header' )( 3326 oSettings, cell, column, classes 3327 ); 3328 } 3329 3330 if ( createHeader ) { 3331 _fnDetectHeader( oSettings.aoHeader, thead ); 3332 } 3333 3334 /* Deal with the footer - add classes if required */ 3335 $(thead).children('tr').children('th, td').addClass( classes.sHeaderTH ); 3336 $(tfoot).children('tr').children('th, td').addClass( classes.sFooterTH ); 3337 3338 // Cache the footer cells. Note that we only take the cells from the first 3339 // row in the footer. If there is more than one row the user wants to 3340 // interact with, they need to use the table().foot() method. Note also this 3341 // allows cells to be used for multiple columns using colspan 3342 if ( tfoot !== null ) { 3343 var cells = oSettings.aoFooter[0]; 3344 3345 for ( i=0, ien=cells.length ; i<ien ; i++ ) { 3346 column = columns[i]; 3347 3348 if (column) { 3349 column.nTf = cells[i].cell; 3350 3351 if ( column.sClass ) { 3352 $(column.nTf).addClass( column.sClass ); 3353 } 3354 } 3355 else { 3356 _fnLog( oSettings, 0, 'Incorrect column count', 18 ); 3357 } 3358 } 3359 } 3360 } 3361 3362 3363 /** 3364 * Draw the header (or footer) element based on the column visibility states. The 3365 * methodology here is to use the layout array from _fnDetectHeader, modified for 3366 * the instantaneous column visibility, to construct the new layout. The grid is 3367 * traversed over cell at a time in a rows x columns grid fashion, although each 3368 * cell insert can cover multiple elements in the grid - which is tracks using the 3369 * aApplied array. Cell inserts in the grid will only occur where there isn't 3370 * already a cell in that position. 3371 * @param {object} oSettings dataTables settings object 3372 * @param array {objects} aoSource Layout array from _fnDetectHeader 3373 * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, 3374 * @memberof DataTable#oApi 3375 */ 3376 function _fnDrawHead( oSettings, aoSource, bIncludeHidden ) 3377 { 3378 var i, iLen, j, jLen, k, kLen, n, nLocalTr; 3379 var aoLocal = []; 3380 var aApplied = []; 3381 var iColumns = oSettings.aoColumns.length; 3382 var iRowspan, iColspan; 3383 3384 if ( ! aoSource ) 3385 { 3386 return; 3387 } 3388 3389 if ( bIncludeHidden === undefined ) 3390 { 3391 bIncludeHidden = false; 3392 } 3393 3394 /* Make a copy of the master layout array, but without the visible columns in it */ 3395 for ( i=0, iLen=aoSource.length ; i<iLen ; i++ ) 3396 { 3397 aoLocal[i] = aoSource[i].slice(); 3398 aoLocal[i].nTr = aoSource[i].nTr; 3399 3400 /* Remove any columns which are currently hidden */ 3401 for ( j=iColumns-1 ; j>=0 ; j-- ) 3402 { 3403 if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden ) 3404 { 3405 aoLocal[i].splice( j, 1 ); 3406 } 3407 } 3408 3409 /* Prep the applied array - it needs an element for each row */ 3410 aApplied.push( [] ); 3411 } 3412 3413 for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ ) 3414 { 3415 nLocalTr = aoLocal[i].nTr; 3416 3417 /* All cells are going to be replaced, so empty out the row */ 3418 if ( nLocalTr ) 3419 { 3420 while( (n = nLocalTr.firstChild) ) 3421 { 3422 nLocalTr.removeChild( n ); 3423 } 3424 } 3425 3426 for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ ) 3427 { 3428 iRowspan = 1; 3429 iColspan = 1; 3430 3431 /* Check to see if there is already a cell (row/colspan) covering our target 3432 * insert point. If there is, then there is nothing to do. 3433 */ 3434 if ( aApplied[i][j] === undefined ) 3435 { 3436 nLocalTr.appendChild( aoLocal[i][j].cell ); 3437 aApplied[i][j] = 1; 3438 3439 /* Expand the cell to cover as many rows as needed */ 3440 while ( aoLocal[i+iRowspan] !== undefined && 3441 aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell ) 3442 { 3443 aApplied[i+iRowspan][j] = 1; 3444 iRowspan++; 3445 } 3446 3447 /* Expand the cell to cover as many columns as needed */ 3448 while ( aoLocal[i][j+iColspan] !== undefined && 3449 aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell ) 3450 { 3451 /* Must update the applied array over the rows for the columns */ 3452 for ( k=0 ; k<iRowspan ; k++ ) 3453 { 3454 aApplied[i+k][j+iColspan] = 1; 3455 } 3456 iColspan++; 3457 } 3458 3459 /* Do the actual expansion in the DOM */ 3460 $(aoLocal[i][j].cell) 3461 .attr('rowspan', iRowspan) 3462 .attr('colspan', iColspan); 3463 } 3464 } 3465 } 3466 } 3467 3468 3469 /** 3470 * Insert the required TR nodes into the table for display 3471 * @param {object} oSettings dataTables settings object 3472 * @param ajaxComplete true after ajax call to complete rendering 3473 * @memberof DataTable#oApi 3474 */ 3475 function _fnDraw( oSettings, ajaxComplete ) 3476 { 3477 // Allow for state saving and a custom start position 3478 _fnStart( oSettings ); 3479 3480 /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ 3481 var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); 3482 if ( $.inArray( false, aPreDraw ) !== -1 ) 3483 { 3484 _fnProcessingDisplay( oSettings, false ); 3485 return; 3486 } 3487 3488 var anRows = []; 3489 var iRowCount = 0; 3490 var asStripeClasses = oSettings.asStripeClasses; 3491 var iStripes = asStripeClasses.length; 3492 var oLang = oSettings.oLanguage; 3493 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 3494 var aiDisplay = oSettings.aiDisplay; 3495 var iDisplayStart = oSettings._iDisplayStart; 3496 var iDisplayEnd = oSettings.fnDisplayEnd(); 3497 3498 oSettings.bDrawing = true; 3499 3500 /* Server-side processing draw intercept */ 3501 if ( oSettings.bDeferLoading ) 3502 { 3503 oSettings.bDeferLoading = false; 3504 oSettings.iDraw++; 3505 _fnProcessingDisplay( oSettings, false ); 3506 } 3507 else if ( !bServerSide ) 3508 { 3509 oSettings.iDraw++; 3510 } 3511 else if ( !oSettings.bDestroying && !ajaxComplete) 3512 { 3513 _fnAjaxUpdate( oSettings ); 3514 return; 3515 } 3516 3517 if ( aiDisplay.length !== 0 ) 3518 { 3519 var iStart = bServerSide ? 0 : iDisplayStart; 3520 var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; 3521 3522 for ( var j=iStart ; j<iEnd ; j++ ) 3523 { 3524 var iDataIndex = aiDisplay[j]; 3525 var aoData = oSettings.aoData[ iDataIndex ]; 3526 if ( aoData.nTr === null ) 3527 { 3528 _fnCreateTr( oSettings, iDataIndex ); 3529 } 3530 3531 var nRow = aoData.nTr; 3532 3533 /* Remove the old striping classes and then add the new one */ 3534 if ( iStripes !== 0 ) 3535 { 3536 var sStripe = asStripeClasses[ iRowCount % iStripes ]; 3537 if ( aoData._sRowStripe != sStripe ) 3538 { 3539 $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe ); 3540 aoData._sRowStripe = sStripe; 3541 } 3542 } 3543 3544 // Row callback functions - might want to manipulate the row 3545 // iRowCount and j are not currently documented. Are they at all 3546 // useful? 3547 _fnCallbackFire( oSettings, 'aoRowCallback', null, 3548 [nRow, aoData._aData, iRowCount, j, iDataIndex] ); 3549 3550 anRows.push( nRow ); 3551 iRowCount++; 3552 } 3553 } 3554 else 3555 { 3556 /* Table is empty - create a row with an empty message in it */ 3557 var sZero = oLang.sZeroRecords; 3558 if ( oSettings.iDraw == 1 && _fnDataSource( oSettings ) == 'ajax' ) 3559 { 3560 sZero = oLang.sLoadingRecords; 3561 } 3562 else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 ) 3563 { 3564 sZero = oLang.sEmptyTable; 3565 } 3566 3567 anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } ) 3568 .append( $('<td />', { 3569 'valign': 'top', 3570 'colSpan': _fnVisbleColumns( oSettings ), 3571 'class': oSettings.oClasses.sRowEmpty 3572 } ).html( sZero ) )[0]; 3573 } 3574 3575 /* Header and footer callbacks */ 3576 _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], 3577 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3578 3579 _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], 3580 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); 3581 3582 var body = $(oSettings.nTBody); 3583 3584 body.children().detach(); 3585 body.append( $(anRows) ); 3586 3587 /* Call all required callback functions for the end of a draw */ 3588 _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] ); 3589 3590 /* Draw is complete, sorting and filtering must be as well */ 3591 oSettings.bSorted = false; 3592 oSettings.bFiltered = false; 3593 oSettings.bDrawing = false; 3594 } 3595 3596 3597 /** 3598 * Redraw the table - taking account of the various features which are enabled 3599 * @param {object} oSettings dataTables settings object 3600 * @param {boolean} [holdPosition] Keep the current paging position. By default 3601 * the paging is reset to the first page 3602 * @memberof DataTable#oApi 3603 */ 3604 function _fnReDraw( settings, holdPosition ) 3605 { 3606 var 3607 features = settings.oFeatures, 3608 sort = features.bSort, 3609 filter = features.bFilter; 3610 3611 if ( sort ) { 3612 _fnSort( settings ); 3613 } 3614 3615 if ( filter ) { 3616 _fnFilterComplete( settings, settings.oPreviousSearch ); 3617 } 3618 else { 3619 // No filtering, so we want to just use the display master 3620 settings.aiDisplay = settings.aiDisplayMaster.slice(); 3621 } 3622 3623 if ( holdPosition !== true ) { 3624 settings._iDisplayStart = 0; 3625 } 3626 3627 // Let any modules know about the draw hold position state (used by 3628 // scrolling internally) 3629 settings._drawHold = holdPosition; 3630 3631 _fnDraw( settings ); 3632 3633 settings._drawHold = false; 3634 } 3635 3636 3637 /** 3638 * Add the options to the page HTML for the table 3639 * @param {object} oSettings dataTables settings object 3640 * @memberof DataTable#oApi 3641 */ 3642 function _fnAddOptionsHtml ( oSettings ) 3643 { 3644 var classes = oSettings.oClasses; 3645 var table = $(oSettings.nTable); 3646 var holding = $('<div/>').insertBefore( table ); // Holding element for speed 3647 var features = oSettings.oFeatures; 3648 3649 // All DataTables are wrapped in a div 3650 var insert = $('<div/>', { 3651 id: oSettings.sTableId+'_wrapper', 3652 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter) 3653 } ); 3654 3655 oSettings.nHolding = holding[0]; 3656 oSettings.nTableWrapper = insert[0]; 3657 oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; 3658 3659 /* Loop over the user set positioning and place the elements as needed */ 3660 var aDom = oSettings.sDom.split(''); 3661 var featureNode, cOption, nNewNode, cNext, sAttr, j; 3662 for ( var i=0 ; i<aDom.length ; i++ ) 3663 { 3664 featureNode = null; 3665 cOption = aDom[i]; 3666 3667 if ( cOption == '<' ) 3668 { 3669 /* New container div */ 3670 nNewNode = $('<div/>')[0]; 3671 3672 /* Check to see if we should append an id and/or a class name to the container */ 3673 cNext = aDom[i+1]; 3674 if ( cNext == "'" || cNext == '"' ) 3675 { 3676 sAttr = ""; 3677 j = 2; 3678 while ( aDom[i+j] != cNext ) 3679 { 3680 sAttr += aDom[i+j]; 3681 j++; 3682 } 3683 3684 /* Replace jQuery UI constants @todo depreciated */ 3685 if ( sAttr == "H" ) 3686 { 3687 sAttr = classes.sJUIHeader; 3688 } 3689 else if ( sAttr == "F" ) 3690 { 3691 sAttr = classes.sJUIFooter; 3692 } 3693 3694 /* The attribute can be in the format of "#id.class", "#id" or "class" This logic 3695 * breaks the string into parts and applies them as needed 3696 */ 3697 if ( sAttr.indexOf('.') != -1 ) 3698 { 3699 var aSplit = sAttr.split('.'); 3700 nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1); 3701 nNewNode.className = aSplit[1]; 3702 } 3703 else if ( sAttr.charAt(0) == "#" ) 3704 { 3705 nNewNode.id = sAttr.substr(1, sAttr.length-1); 3706 } 3707 else 3708 { 3709 nNewNode.className = sAttr; 3710 } 3711 3712 i += j; /* Move along the position array */ 3713 } 3714 3715 insert.append( nNewNode ); 3716 insert = $(nNewNode); 3717 } 3718 else if ( cOption == '>' ) 3719 { 3720 /* End container div */ 3721 insert = insert.parent(); 3722 } 3723 // @todo Move options into their own plugins? 3724 else if ( cOption == 'l' && features.bPaginate && features.bLengthChange ) 3725 { 3726 /* Length */ 3727 featureNode = _fnFeatureHtmlLength( oSettings ); 3728 } 3729 else if ( cOption == 'f' && features.bFilter ) 3730 { 3731 /* Filter */ 3732 featureNode = _fnFeatureHtmlFilter( oSettings ); 3733 } 3734 else if ( cOption == 'r' && features.bProcessing ) 3735 { 3736 /* pRocessing */ 3737 featureNode = _fnFeatureHtmlProcessing( oSettings ); 3738 } 3739 else if ( cOption == 't' ) 3740 { 3741 /* Table */ 3742 featureNode = _fnFeatureHtmlTable( oSettings ); 3743 } 3744 else if ( cOption == 'i' && features.bInfo ) 3745 { 3746 /* Info */ 3747 featureNode = _fnFeatureHtmlInfo( oSettings ); 3748 } 3749 else if ( cOption == 'p' && features.bPaginate ) 3750 { 3751 /* Pagination */ 3752 featureNode = _fnFeatureHtmlPaginate( oSettings ); 3753 } 3754 else if ( DataTable.ext.feature.length !== 0 ) 3755 { 3756 /* Plug-in features */ 3757 var aoFeatures = DataTable.ext.feature; 3758 for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ ) 3759 { 3760 if ( cOption == aoFeatures[k].cFeature ) 3761 { 3762 featureNode = aoFeatures[k].fnInit( oSettings ); 3763 break; 3764 } 3765 } 3766 } 3767 3768 /* Add to the 2D features array */ 3769 if ( featureNode ) 3770 { 3771 var aanFeatures = oSettings.aanFeatures; 3772 3773 if ( ! aanFeatures[cOption] ) 3774 { 3775 aanFeatures[cOption] = []; 3776 } 3777 3778 aanFeatures[cOption].push( featureNode ); 3779 insert.append( featureNode ); 3780 } 3781 } 3782 3783 /* Built our DOM structure - replace the holding div with what we want */ 3784 holding.replaceWith( insert ); 3785 oSettings.nHolding = null; 3786 } 3787 3788 3789 /** 3790 * Use the DOM source to create up an array of header cells. The idea here is to 3791 * create a layout grid (array) of rows x columns, which contains a reference 3792 * to the cell that that point in the grid (regardless of col/rowspan), such that 3793 * any column / row could be removed and the new grid constructed 3794 * @param array {object} aLayout Array to store the calculated layout in 3795 * @param {node} nThead The header/footer element for the table 3796 * @memberof DataTable#oApi 3797 */ 3798 function _fnDetectHeader ( aLayout, nThead ) 3799 { 3800 var nTrs = $(nThead).children('tr'); 3801 var nTr, nCell; 3802 var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; 3803 var bUnique; 3804 var fnShiftCol = function ( a, i, j ) { 3805 var k = a[i]; 3806 while ( k[j] ) { 3807 j++; 3808 } 3809 return j; 3810 }; 3811 3812 aLayout.splice( 0, aLayout.length ); 3813 3814 /* We know how many rows there are in the layout - so prep it */ 3815 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 3816 { 3817 aLayout.push( [] ); 3818 } 3819 3820 /* Calculate a layout array */ 3821 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 3822 { 3823 nTr = nTrs[i]; 3824 iColumn = 0; 3825 3826 /* For every cell in the row... */ 3827 nCell = nTr.firstChild; 3828 while ( nCell ) { 3829 if ( nCell.nodeName.toUpperCase() == "TD" || 3830 nCell.nodeName.toUpperCase() == "TH" ) 3831 { 3832 /* Get the col and rowspan attributes from the DOM and sanitise them */ 3833 iColspan = nCell.getAttribute('colspan') * 1; 3834 iRowspan = nCell.getAttribute('rowspan') * 1; 3835 iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan; 3836 iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan; 3837 3838 /* There might be colspan cells already in this row, so shift our target 3839 * accordingly 3840 */ 3841 iColShifted = fnShiftCol( aLayout, i, iColumn ); 3842 3843 /* Cache calculation for unique columns */ 3844 bUnique = iColspan === 1 ? true : false; 3845 3846 /* If there is col / rowspan, copy the information into the layout grid */ 3847 for ( l=0 ; l<iColspan ; l++ ) 3848 { 3849 for ( k=0 ; k<iRowspan ; k++ ) 3850 { 3851 aLayout[i+k][iColShifted+l] = { 3852 "cell": nCell, 3853 "unique": bUnique 3854 }; 3855 aLayout[i+k].nTr = nTr; 3856 } 3857 } 3858 } 3859 nCell = nCell.nextSibling; 3860 } 3861 } 3862 } 3863 3864 3865 /** 3866 * Get an array of unique th elements, one for each column 3867 * @param {object} oSettings dataTables settings object 3868 * @param {node} nHeader automatically detect the layout from this node - optional 3869 * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional 3870 * @returns array {node} aReturn list of unique th's 3871 * @memberof DataTable#oApi 3872 */ 3873 function _fnGetUniqueThs ( oSettings, nHeader, aLayout ) 3874 { 3875 var aReturn = []; 3876 if ( !aLayout ) 3877 { 3878 aLayout = oSettings.aoHeader; 3879 if ( nHeader ) 3880 { 3881 aLayout = []; 3882 _fnDetectHeader( aLayout, nHeader ); 3883 } 3884 } 3885 3886 for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ ) 3887 { 3888 for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ ) 3889 { 3890 if ( aLayout[i][j].unique && 3891 (!aReturn[j] || !oSettings.bSortCellsTop) ) 3892 { 3893 aReturn[j] = aLayout[i][j].cell; 3894 } 3895 } 3896 } 3897 3898 return aReturn; 3899 } 3900 3901 /** 3902 * Set the start position for draw 3903 * @param {object} oSettings dataTables settings object 3904 */ 3905 function _fnStart( oSettings ) 3906 { 3907 var bServerSide = _fnDataSource( oSettings ) == 'ssp'; 3908 var iInitDisplayStart = oSettings.iInitDisplayStart; 3909 3910 // Check and see if we have an initial draw position from state saving 3911 if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 ) 3912 { 3913 oSettings._iDisplayStart = bServerSide ? 3914 iInitDisplayStart : 3915 iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 3916 0 : 3917 iInitDisplayStart; 3918 3919 oSettings.iInitDisplayStart = -1; 3920 } 3921 } 3922 3923 /** 3924 * Create an Ajax call based on the table's settings, taking into account that 3925 * parameters can have multiple forms, and backwards compatibility. 3926 * 3927 * @param {object} oSettings dataTables settings object 3928 * @param {array} data Data to send to the server, required by 3929 * DataTables - may be augmented by developer callbacks 3930 * @param {function} fn Callback function to run when data is obtained 3931 */ 3932 function _fnBuildAjax( oSettings, data, fn ) 3933 { 3934 // Compatibility with 1.9-, allow fnServerData and event to manipulate 3935 _fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] ); 3936 3937 // Convert to object based for 1.10+ if using the old array scheme which can 3938 // come from server-side processing or serverParams 3939 if ( data && Array.isArray(data) ) { 3940 var tmp = {}; 3941 var rbracket = /(.*?)\[\]$/; 3942 3943 $.each( data, function (key, val) { 3944 var match = val.name.match(rbracket); 3945 3946 if ( match ) { 3947 // Support for arrays 3948 var name = match[0]; 3949 3950 if ( ! tmp[ name ] ) { 3951 tmp[ name ] = []; 3952 } 3953 tmp[ name ].push( val.value ); 3954 } 3955 else { 3956 tmp[val.name] = val.value; 3957 } 3958 } ); 3959 data = tmp; 3960 } 3961 3962 var ajaxData; 3963 var ajax = oSettings.ajax; 3964 var instance = oSettings.oInstance; 3965 var callback = function ( json ) { 3966 var status = oSettings.jqXHR 3967 ? oSettings.jqXHR.status 3968 : null; 3969 3970 if ( json === null || (typeof status === 'number' && status == 204 ) ) { 3971 json = {}; 3972 _fnAjaxDataSrc( oSettings, json, [] ); 3973 } 3974 3975 var error = json.error || json.sError; 3976 if ( error ) { 3977 _fnLog( oSettings, 0, error ); 3978 } 3979 3980 oSettings.json = json; 3981 3982 _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] ); 3983 fn( json ); 3984 }; 3985 3986 if ( $.isPlainObject( ajax ) && ajax.data ) 3987 { 3988 ajaxData = ajax.data; 3989 3990 var newData = typeof ajaxData === 'function' ? 3991 ajaxData( data, oSettings ) : // fn can manipulate data or return 3992 ajaxData; // an object object or array to merge 3993 3994 // If the function returned something, use that alone 3995 data = typeof ajaxData === 'function' && newData ? 3996 newData : 3997 $.extend( true, data, newData ); 3998 3999 // Remove the data property as we've resolved it already and don't want 4000 // jQuery to do it again (it is restored at the end of the function) 4001 delete ajax.data; 4002 } 4003 4004 var baseAjax = { 4005 "data": data, 4006 "success": callback, 4007 "dataType": "json", 4008 "cache": false, 4009 "type": oSettings.sServerMethod, 4010 "error": function (xhr, error, thrown) { 4011 var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] ); 4012 4013 if ( $.inArray( true, ret ) === -1 ) { 4014 if ( error == "parsererror" ) { 4015 _fnLog( oSettings, 0, 'Invalid JSON response', 1 ); 4016 } 4017 else if ( xhr.readyState === 4 ) { 4018 _fnLog( oSettings, 0, 'Ajax error', 7 ); 4019 } 4020 } 4021 4022 _fnProcessingDisplay( oSettings, false ); 4023 } 4024 }; 4025 4026 // Store the data submitted for the API 4027 oSettings.oAjaxData = data; 4028 4029 // Allow plug-ins and external processes to modify the data 4030 _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] ); 4031 4032 if ( oSettings.fnServerData ) 4033 { 4034 // DataTables 1.9- compatibility 4035 oSettings.fnServerData.call( instance, 4036 oSettings.sAjaxSource, 4037 $.map( data, function (val, key) { // Need to convert back to 1.9 trad format 4038 return { name: key, value: val }; 4039 } ), 4040 callback, 4041 oSettings 4042 ); 4043 } 4044 else if ( oSettings.sAjaxSource || typeof ajax === 'string' ) 4045 { 4046 // DataTables 1.9- compatibility 4047 oSettings.jqXHR = $.ajax( $.extend( baseAjax, { 4048 url: ajax || oSettings.sAjaxSource 4049 } ) ); 4050 } 4051 else if ( typeof ajax === 'function' ) 4052 { 4053 // Is a function - let the caller define what needs to be done 4054 oSettings.jqXHR = ajax.call( instance, data, callback, oSettings ); 4055 } 4056 else 4057 { 4058 // Object to extend the base settings 4059 oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) ); 4060 4061 // Restore for next time around 4062 ajax.data = ajaxData; 4063 } 4064 } 4065 4066 4067 /** 4068 * Update the table using an Ajax call 4069 * @param {object} settings dataTables settings object 4070 * @returns {boolean} Block the table drawing or not 4071 * @memberof DataTable#oApi 4072 */ 4073 function _fnAjaxUpdate( settings ) 4074 { 4075 settings.iDraw++; 4076 _fnProcessingDisplay( settings, true ); 4077 4078 _fnBuildAjax( 4079 settings, 4080 _fnAjaxParameters( settings ), 4081 function(json) { 4082 _fnAjaxUpdateDraw( settings, json ); 4083 } 4084 ); 4085 } 4086 4087 4088 /** 4089 * Build up the parameters in an object needed for a server-side processing 4090 * request. Note that this is basically done twice, is different ways - a modern 4091 * method which is used by default in DataTables 1.10 which uses objects and 4092 * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if 4093 * the sAjaxSource option is used in the initialisation, or the legacyAjax 4094 * option is set. 4095 * @param {object} oSettings dataTables settings object 4096 * @returns {bool} block the table drawing or not 4097 * @memberof DataTable#oApi 4098 */ 4099 function _fnAjaxParameters( settings ) 4100 { 4101 var 4102 columns = settings.aoColumns, 4103 columnCount = columns.length, 4104 features = settings.oFeatures, 4105 preSearch = settings.oPreviousSearch, 4106 preColSearch = settings.aoPreSearchCols, 4107 i, data = [], dataProp, column, columnSearch, 4108 sort = _fnSortFlatten( settings ), 4109 displayStart = settings._iDisplayStart, 4110 displayLength = features.bPaginate !== false ? 4111 settings._iDisplayLength : 4112 -1; 4113 4114 var param = function ( name, value ) { 4115 data.push( { 'name': name, 'value': value } ); 4116 }; 4117 4118 // DataTables 1.9- compatible method 4119 param( 'sEcho', settings.iDraw ); 4120 param( 'iColumns', columnCount ); 4121 param( 'sColumns', _pluck( columns, 'sName' ).join(',') ); 4122 param( 'iDisplayStart', displayStart ); 4123 param( 'iDisplayLength', displayLength ); 4124 4125 // DataTables 1.10+ method 4126 var d = { 4127 draw: settings.iDraw, 4128 columns: [], 4129 order: [], 4130 start: displayStart, 4131 length: displayLength, 4132 search: { 4133 value: preSearch.sSearch, 4134 regex: preSearch.bRegex 4135 } 4136 }; 4137 4138 for ( i=0 ; i<columnCount ; i++ ) { 4139 column = columns[i]; 4140 columnSearch = preColSearch[i]; 4141 dataProp = typeof column.mData=="function" ? 'function' : column.mData ; 4142 4143 d.columns.push( { 4144 data: dataProp, 4145 name: column.sName, 4146 searchable: column.bSearchable, 4147 orderable: column.bSortable, 4148 search: { 4149 value: columnSearch.sSearch, 4150 regex: columnSearch.bRegex 4151 } 4152 } ); 4153 4154 param( "mDataProp_"+i, dataProp ); 4155 4156 if ( features.bFilter ) { 4157 param( 'sSearch_'+i, columnSearch.sSearch ); 4158 param( 'bRegex_'+i, columnSearch.bRegex ); 4159 param( 'bSearchable_'+i, column.bSearchable ); 4160 } 4161 4162 if ( features.bSort ) { 4163 param( 'bSortable_'+i, column.bSortable ); 4164 } 4165 } 4166 4167 if ( features.bFilter ) { 4168 param( 'sSearch', preSearch.sSearch ); 4169 param( 'bRegex', preSearch.bRegex ); 4170 } 4171 4172 if ( features.bSort ) { 4173 $.each( sort, function ( i, val ) { 4174 d.order.push( { column: val.col, dir: val.dir } ); 4175 4176 param( 'iSortCol_'+i, val.col ); 4177 param( 'sSortDir_'+i, val.dir ); 4178 } ); 4179 4180 param( 'iSortingCols', sort.length ); 4181 } 4182 4183 // If the legacy.ajax parameter is null, then we automatically decide which 4184 // form to use, based on sAjaxSource 4185 var legacy = DataTable.ext.legacy.ajax; 4186 if ( legacy === null ) { 4187 return settings.sAjaxSource ? data : d; 4188 } 4189 4190 // Otherwise, if legacy has been specified then we use that to decide on the 4191 // form 4192 return legacy ? data : d; 4193 } 4194 4195 4196 /** 4197 * Data the data from the server (nuking the old) and redraw the table 4198 * @param {object} oSettings dataTables settings object 4199 * @param {object} json json data return from the server. 4200 * @param {string} json.sEcho Tracking flag for DataTables to match requests 4201 * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering 4202 * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering 4203 * @param {array} json.aaData The data to display on this page 4204 * @param {string} [json.sColumns] Column ordering (sName, comma separated) 4205 * @memberof DataTable#oApi 4206 */ 4207 function _fnAjaxUpdateDraw ( settings, json ) 4208 { 4209 // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. 4210 // Support both 4211 var compat = function ( old, modern ) { 4212 return json[old] !== undefined ? json[old] : json[modern]; 4213 }; 4214 4215 var data = _fnAjaxDataSrc( settings, json ); 4216 var draw = compat( 'sEcho', 'draw' ); 4217 var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' ); 4218 var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' ); 4219 4220 if ( draw !== undefined ) { 4221 // Protect against out of sequence returns 4222 if ( draw*1 < settings.iDraw ) { 4223 return; 4224 } 4225 settings.iDraw = draw * 1; 4226 } 4227 4228 // No data in returned object, so rather than an array, we show an empty table 4229 if ( ! data ) { 4230 data = []; 4231 } 4232 4233 _fnClearTable( settings ); 4234 settings._iRecordsTotal = parseInt(recordsTotal, 10); 4235 settings._iRecordsDisplay = parseInt(recordsFiltered, 10); 4236 4237 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 4238 _fnAddData( settings, data[i] ); 4239 } 4240 settings.aiDisplay = settings.aiDisplayMaster.slice(); 4241 4242 _fnDraw( settings, true ); 4243 4244 if ( ! settings._bInitComplete ) { 4245 _fnInitComplete( settings, json ); 4246 } 4247 4248 _fnProcessingDisplay( settings, false ); 4249 } 4250 4251 4252 /** 4253 * Get the data from the JSON data source to use for drawing a table. Using 4254 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the 4255 * source object, or from a processing function. 4256 * @param {object} oSettings dataTables settings object 4257 * @param {object} json Data source object / array from the server 4258 * @return {array} Array of data to use 4259 */ 4260 function _fnAjaxDataSrc ( oSettings, json, write ) 4261 { 4262 var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ? 4263 oSettings.ajax.dataSrc : 4264 oSettings.sAjaxDataProp; // Compatibility with 1.9-. 4265 4266 if ( ! write ) { 4267 if ( dataSrc === 'data' ) { 4268 // If the default, then we still want to support the old style, and safely ignore 4269 // it if possible 4270 return json.aaData || json[dataSrc]; 4271 } 4272 4273 return dataSrc !== "" ? 4274 _fnGetObjectDataFn( dataSrc )( json ) : 4275 json; 4276 } 4277 4278 // set 4279 _fnSetObjectDataFn( dataSrc )( json, write ); 4280 } 4281 4282 /** 4283 * Generate the node required for filtering text 4284 * @returns {node} Filter control element 4285 * @param {object} oSettings dataTables settings object 4286 * @memberof DataTable#oApi 4287 */ 4288 function _fnFeatureHtmlFilter ( settings ) 4289 { 4290 var classes = settings.oClasses; 4291 var tableId = settings.sTableId; 4292 var language = settings.oLanguage; 4293 var previousSearch = settings.oPreviousSearch; 4294 var features = settings.aanFeatures; 4295 var input = '<input type="search" class="'+classes.sFilterInput+'"/>'; 4296 4297 var str = language.sSearch; 4298 str = str.match(/_INPUT_/) ? 4299 str.replace('_INPUT_', input) : 4300 str+input; 4301 4302 var filter = $('<div/>', { 4303 'id': ! features.f ? tableId+'_filter' : null, 4304 'class': classes.sFilter 4305 } ) 4306 .append( $('<label/>' ).append( str ) ); 4307 4308 var searchFn = function(event) { 4309 /* Update all other filter input elements for the new display */ 4310 var n = features.f; 4311 var val = !this.value ? "" : this.value; // mental IE8 fix :-( 4312 if(previousSearch.return && event.key !== "Enter") { 4313 return; 4314 } 4315 /* Now do the filter */ 4316 if ( val != previousSearch.sSearch ) { 4317 _fnFilterComplete( settings, { 4318 "sSearch": val, 4319 "bRegex": previousSearch.bRegex, 4320 "bSmart": previousSearch.bSmart , 4321 "bCaseInsensitive": previousSearch.bCaseInsensitive, 4322 "return": previousSearch.return 4323 } ); 4324 4325 // Need to redraw, without resorting 4326 settings._iDisplayStart = 0; 4327 _fnDraw( settings ); 4328 } 4329 }; 4330 4331 var searchDelay = settings.searchDelay !== null ? 4332 settings.searchDelay : 4333 _fnDataSource( settings ) === 'ssp' ? 4334 400 : 4335 0; 4336 4337 var jqFilter = $('input', filter) 4338 .val( previousSearch.sSearch ) 4339 .attr( 'placeholder', language.sSearchPlaceholder ) 4340 .on( 4341 'keyup.DT search.DT input.DT paste.DT cut.DT', 4342 searchDelay ? 4343 _fnThrottle( searchFn, searchDelay ) : 4344 searchFn 4345 ) 4346 .on( 'mouseup', function(e) { 4347 // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking 4348 // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` 4349 // checks the value to see if it has changed. In other browsers it won't have. 4350 setTimeout( function () { 4351 searchFn.call(jqFilter[0], e); 4352 }, 10); 4353 } ) 4354 .on( 'keypress.DT', function(e) { 4355 /* Prevent form submission */ 4356 if ( e.keyCode == 13 ) { 4357 return false; 4358 } 4359 } ) 4360 .attr('aria-controls', tableId); 4361 4362 // Update the input elements whenever the table is filtered 4363 $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { 4364 if ( settings === s ) { 4365 // IE9 throws an 'unknown error' if document.activeElement is used 4366 // inside an iframe or frame... 4367 try { 4368 if ( jqFilter[0] !== document.activeElement ) { 4369 jqFilter.val( previousSearch.sSearch ); 4370 } 4371 } 4372 catch ( e ) {} 4373 } 4374 } ); 4375 4376 return filter[0]; 4377 } 4378 4379 4380 /** 4381 * Filter the table using both the global filter and column based filtering 4382 * @param {object} oSettings dataTables settings object 4383 * @param {object} oSearch search information 4384 * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0) 4385 * @memberof DataTable#oApi 4386 */ 4387 function _fnFilterComplete ( oSettings, oInput, iForce ) 4388 { 4389 var oPrevSearch = oSettings.oPreviousSearch; 4390 var aoPrevSearch = oSettings.aoPreSearchCols; 4391 var fnSaveFilter = function ( oFilter ) { 4392 /* Save the filtering values */ 4393 oPrevSearch.sSearch = oFilter.sSearch; 4394 oPrevSearch.bRegex = oFilter.bRegex; 4395 oPrevSearch.bSmart = oFilter.bSmart; 4396 oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive; 4397 oPrevSearch.return = oFilter.return; 4398 }; 4399 var fnRegex = function ( o ) { 4400 // Backwards compatibility with the bEscapeRegex option 4401 return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex; 4402 }; 4403 4404 // Resolve any column types that are unknown due to addition or invalidation 4405 // @todo As per sort - can this be moved into an event handler? 4406 _fnColumnTypes( oSettings ); 4407 4408 /* In server-side processing all filtering is done by the server, so no point hanging around here */ 4409 if ( _fnDataSource( oSettings ) != 'ssp' ) 4410 { 4411 /* Global filter */ 4412 _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive, oInput.return ); 4413 fnSaveFilter( oInput ); 4414 4415 /* Now do the individual column filter */ 4416 for ( var i=0 ; i<aoPrevSearch.length ; i++ ) 4417 { 4418 _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]), 4419 aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive ); 4420 } 4421 4422 /* Custom filtering */ 4423 _fnFilterCustom( oSettings ); 4424 } 4425 else 4426 { 4427 fnSaveFilter( oInput ); 4428 } 4429 4430 /* Tell the draw function we have been filtering */ 4431 oSettings.bFiltered = true; 4432 _fnCallbackFire( oSettings, null, 'search', [oSettings] ); 4433 } 4434 4435 4436 /** 4437 * Apply custom filtering functions 4438 * @param {object} oSettings dataTables settings object 4439 * @memberof DataTable#oApi 4440 */ 4441 function _fnFilterCustom( settings ) 4442 { 4443 var filters = DataTable.ext.search; 4444 var displayRows = settings.aiDisplay; 4445 var row, rowIdx; 4446 4447 for ( var i=0, ien=filters.length ; i<ien ; i++ ) { 4448 var rows = []; 4449 4450 // Loop over each row and see if it should be included 4451 for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { 4452 rowIdx = displayRows[ j ]; 4453 row = settings.aoData[ rowIdx ]; 4454 4455 if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { 4456 rows.push( rowIdx ); 4457 } 4458 } 4459 4460 // So the array reference doesn't break set the results into the 4461 // existing array 4462 displayRows.length = 0; 4463 $.merge( displayRows, rows ); 4464 } 4465 } 4466 4467 4468 /** 4469 * Filter the table on a per-column basis 4470 * @param {object} oSettings dataTables settings object 4471 * @param {string} sInput string to filter on 4472 * @param {int} iColumn column to filter 4473 * @param {bool} bRegex treat search string as a regular expression or not 4474 * @param {bool} bSmart use smart filtering or not 4475 * @param {bool} bCaseInsensitive Do case insensitive matching or not 4476 * @memberof DataTable#oApi 4477 */ 4478 function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive ) 4479 { 4480 if ( searchStr === '' ) { 4481 return; 4482 } 4483 4484 var data; 4485 var out = []; 4486 var display = settings.aiDisplay; 4487 var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive ); 4488 4489 for ( var i=0 ; i<display.length ; i++ ) { 4490 data = settings.aoData[ display[i] ]._aFilterData[ colIdx ]; 4491 4492 if ( rpSearch.test( data ) ) { 4493 out.push( display[i] ); 4494 } 4495 } 4496 4497 settings.aiDisplay = out; 4498 } 4499 4500 4501 /** 4502 * Filter the data table based on user input and draw the table 4503 * @param {object} settings dataTables settings object 4504 * @param {string} input string to filter on 4505 * @param {int} force optional - force a research of the master array (1) or not (undefined or 0) 4506 * @param {bool} regex treat as a regular expression or not 4507 * @param {bool} smart perform smart filtering or not 4508 * @param {bool} caseInsensitive Do case insensitive matching or not 4509 * @memberof DataTable#oApi 4510 */ 4511 function _fnFilter( settings, input, force, regex, smart, caseInsensitive ) 4512 { 4513 var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive ); 4514 var prevSearch = settings.oPreviousSearch.sSearch; 4515 var displayMaster = settings.aiDisplayMaster; 4516 var display, invalidated, i; 4517 var filtered = []; 4518 4519 // Need to take account of custom filtering functions - always filter 4520 if ( DataTable.ext.search.length !== 0 ) { 4521 force = true; 4522 } 4523 4524 // Check if any of the rows were invalidated 4525 invalidated = _fnFilterData( settings ); 4526 4527 // If the input is blank - we just want the full data set 4528 if ( input.length <= 0 ) { 4529 settings.aiDisplay = displayMaster.slice(); 4530 } 4531 else { 4532 // New search - start from the master array 4533 if ( invalidated || 4534 force || 4535 regex || 4536 prevSearch.length > input.length || 4537 input.indexOf(prevSearch) !== 0 || 4538 settings.bSorted // On resort, the display master needs to be 4539 // re-filtered since indexes will have changed 4540 ) { 4541 settings.aiDisplay = displayMaster.slice(); 4542 } 4543 4544 // Search the display array 4545 display = settings.aiDisplay; 4546 4547 for ( i=0 ; i<display.length ; i++ ) { 4548 if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) { 4549 filtered.push( display[i] ); 4550 } 4551 } 4552 4553 settings.aiDisplay = filtered; 4554 } 4555 } 4556 4557 4558 /** 4559 * Build a regular expression object suitable for searching a table 4560 * @param {string} sSearch string to search for 4561 * @param {bool} bRegex treat as a regular expression or not 4562 * @param {bool} bSmart perform smart filtering or not 4563 * @param {bool} bCaseInsensitive Do case insensitive matching or not 4564 * @returns {RegExp} constructed object 4565 * @memberof DataTable#oApi 4566 */ 4567 function _fnFilterCreateSearch( search, regex, smart, caseInsensitive ) 4568 { 4569 search = regex ? 4570 search : 4571 _fnEscapeRegex( search ); 4572 4573 if ( smart ) { 4574 /* For smart filtering we want to allow the search to work regardless of 4575 * word order. We also want double quoted text to be preserved, so word 4576 * order is important - a la google. So this is what we want to 4577 * generate: 4578 * 4579 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ 4580 */ 4581 var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) { 4582 if ( word.charAt(0) === '"' ) { 4583 var m = word.match( /^"(.*)"$/ ); 4584 word = m ? m[1] : word; 4585 } 4586 4587 return word.replace('"', ''); 4588 } ); 4589 4590 search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$'; 4591 } 4592 4593 return new RegExp( search, caseInsensitive ? 'i' : '' ); 4594 } 4595 4596 4597 /** 4598 * Escape a string such that it can be used in a regular expression 4599 * @param {string} sVal string to escape 4600 * @returns {string} escaped string 4601 * @memberof DataTable#oApi 4602 */ 4603 var _fnEscapeRegex = DataTable.util.escapeRegex; 4604 4605 var __filter_div = $('<div>')[0]; 4606 var __filter_div_textContent = __filter_div.textContent !== undefined; 4607 4608 // Update the filtering data for each row if needed (by invalidation or first run) 4609 function _fnFilterData ( settings ) 4610 { 4611 var columns = settings.aoColumns; 4612 var column; 4613 var i, j, ien, jen, filterData, cellData, row; 4614 var wasInvalidated = false; 4615 4616 for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 4617 row = settings.aoData[i]; 4618 4619 if ( ! row._aFilterData ) { 4620 filterData = []; 4621 4622 for ( j=0, jen=columns.length ; j<jen ; j++ ) { 4623 column = columns[j]; 4624 4625 if ( column.bSearchable ) { 4626 cellData = _fnGetCellData( settings, i, j, 'filter' ); 4627 4628 // Search in DataTables 1.10 is string based. In 1.11 this 4629 // should be altered to also allow strict type checking. 4630 if ( cellData === null ) { 4631 cellData = ''; 4632 } 4633 4634 if ( typeof cellData !== 'string' && cellData.toString ) { 4635 cellData = cellData.toString(); 4636 } 4637 } 4638 else { 4639 cellData = ''; 4640 } 4641 4642 // If it looks like there is an HTML entity in the string, 4643 // attempt to decode it so sorting works as expected. Note that 4644 // we could use a single line of jQuery to do this, but the DOM 4645 // method used here is much faster http://jsperf.com/html-decode 4646 if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { 4647 __filter_div.innerHTML = cellData; 4648 cellData = __filter_div_textContent ? 4649 __filter_div.textContent : 4650 __filter_div.innerText; 4651 } 4652 4653 if ( cellData.replace ) { 4654 cellData = cellData.replace(/[\r\n\u2028]/g, ''); 4655 } 4656 4657 filterData.push( cellData ); 4658 } 4659 4660 row._aFilterData = filterData; 4661 row._sFilterRow = filterData.join(' '); 4662 wasInvalidated = true; 4663 } 4664 } 4665 4666 return wasInvalidated; 4667 } 4668 4669 4670 /** 4671 * Convert from the internal Hungarian notation to camelCase for external 4672 * interaction 4673 * @param {object} obj Object to convert 4674 * @returns {object} Inverted object 4675 * @memberof DataTable#oApi 4676 */ 4677 function _fnSearchToCamel ( obj ) 4678 { 4679 return { 4680 search: obj.sSearch, 4681 smart: obj.bSmart, 4682 regex: obj.bRegex, 4683 caseInsensitive: obj.bCaseInsensitive 4684 }; 4685 } 4686 4687 4688 4689 /** 4690 * Convert from camelCase notation to the internal Hungarian. We could use the 4691 * Hungarian convert function here, but this is cleaner 4692 * @param {object} obj Object to convert 4693 * @returns {object} Inverted object 4694 * @memberof DataTable#oApi 4695 */ 4696 function _fnSearchToHung ( obj ) 4697 { 4698 return { 4699 sSearch: obj.search, 4700 bSmart: obj.smart, 4701 bRegex: obj.regex, 4702 bCaseInsensitive: obj.caseInsensitive 4703 }; 4704 } 4705 4706 /** 4707 * Generate the node required for the info display 4708 * @param {object} oSettings dataTables settings object 4709 * @returns {node} Information element 4710 * @memberof DataTable#oApi 4711 */ 4712 function _fnFeatureHtmlInfo ( settings ) 4713 { 4714 var 4715 tid = settings.sTableId, 4716 nodes = settings.aanFeatures.i, 4717 n = $('<div/>', { 4718 'class': settings.oClasses.sInfo, 4719 'id': ! nodes ? tid+'_info' : null 4720 } ); 4721 4722 if ( ! nodes ) { 4723 // Update display on each draw 4724 settings.aoDrawCallback.push( { 4725 "fn": _fnUpdateInfo, 4726 "sName": "information" 4727 } ); 4728 4729 n 4730 .attr( 'role', 'status' ) 4731 .attr( 'aria-live', 'polite' ); 4732 4733 // Table is described by our info div 4734 $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); 4735 } 4736 4737 return n[0]; 4738 } 4739 4740 4741 /** 4742 * Update the information elements in the display 4743 * @param {object} settings dataTables settings object 4744 * @memberof DataTable#oApi 4745 */ 4746 function _fnUpdateInfo ( settings ) 4747 { 4748 /* Show information about the table */ 4749 var nodes = settings.aanFeatures.i; 4750 if ( nodes.length === 0 ) { 4751 return; 4752 } 4753 4754 var 4755 lang = settings.oLanguage, 4756 start = settings._iDisplayStart+1, 4757 end = settings.fnDisplayEnd(), 4758 max = settings.fnRecordsTotal(), 4759 total = settings.fnRecordsDisplay(), 4760 out = total ? 4761 lang.sInfo : 4762 lang.sInfoEmpty; 4763 4764 if ( total !== max ) { 4765 /* Record set after filtering */ 4766 out += ' ' + lang.sInfoFiltered; 4767 } 4768 4769 // Convert the macros 4770 out += lang.sInfoPostFix; 4771 out = _fnInfoMacros( settings, out ); 4772 4773 var callback = lang.fnInfoCallback; 4774 if ( callback !== null ) { 4775 out = callback.call( settings.oInstance, 4776 settings, start, end, max, total, out 4777 ); 4778 } 4779 4780 $(nodes).html( out ); 4781 } 4782 4783 4784 function _fnInfoMacros ( settings, str ) 4785 { 4786 // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only 4787 // internally 4788 var 4789 formatter = settings.fnFormatNumber, 4790 start = settings._iDisplayStart+1, 4791 len = settings._iDisplayLength, 4792 vis = settings.fnRecordsDisplay(), 4793 all = len === -1; 4794 4795 return str. 4796 replace(/_START_/g, formatter.call( settings, start ) ). 4797 replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). 4798 replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ). 4799 replace(/_TOTAL_/g, formatter.call( settings, vis ) ). 4800 replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). 4801 replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ); 4802 } 4803 4804 4805 4806 /** 4807 * Draw the table for the first time, adding all required features 4808 * @param {object} settings dataTables settings object 4809 * @memberof DataTable#oApi 4810 */ 4811 function _fnInitialise ( settings ) 4812 { 4813 var i, iLen, iAjaxStart=settings.iInitDisplayStart; 4814 var columns = settings.aoColumns, column; 4815 var features = settings.oFeatures; 4816 var deferLoading = settings.bDeferLoading; // value modified by the draw 4817 4818 /* Ensure that the table data is fully initialised */ 4819 if ( ! settings.bInitialised ) { 4820 setTimeout( function(){ _fnInitialise( settings ); }, 200 ); 4821 return; 4822 } 4823 4824 /* Show the display HTML options */ 4825 _fnAddOptionsHtml( settings ); 4826 4827 /* Build and draw the header / footer for the table */ 4828 _fnBuildHead( settings ); 4829 _fnDrawHead( settings, settings.aoHeader ); 4830 _fnDrawHead( settings, settings.aoFooter ); 4831 4832 /* Okay to show that something is going on now */ 4833 _fnProcessingDisplay( settings, true ); 4834 4835 /* Calculate sizes for columns */ 4836 if ( features.bAutoWidth ) { 4837 _fnCalculateColumnWidths( settings ); 4838 } 4839 4840 for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { 4841 column = columns[i]; 4842 4843 if ( column.sWidth ) { 4844 column.nTh.style.width = _fnStringToCss( column.sWidth ); 4845 } 4846 } 4847 4848 _fnCallbackFire( settings, null, 'preInit', [settings] ); 4849 4850 // If there is default sorting required - let's do it. The sort function 4851 // will do the drawing for us. Otherwise we draw the table regardless of the 4852 // Ajax source - this allows the table to look initialised for Ajax sourcing 4853 // data (show 'loading' message possibly) 4854 _fnReDraw( settings ); 4855 4856 // Server-side processing init complete is done by _fnAjaxUpdateDraw 4857 var dataSrc = _fnDataSource( settings ); 4858 if ( dataSrc != 'ssp' || deferLoading ) { 4859 // if there is an ajax source load the data 4860 if ( dataSrc == 'ajax' ) { 4861 _fnBuildAjax( settings, [], function(json) { 4862 var aData = _fnAjaxDataSrc( settings, json ); 4863 4864 // Got the data - add it to the table 4865 for ( i=0 ; i<aData.length ; i++ ) { 4866 _fnAddData( settings, aData[i] ); 4867 } 4868 4869 // Reset the init display for cookie saving. We've already done 4870 // a filter, and therefore cleared it before. So we need to make 4871 // it appear 'fresh' 4872 settings.iInitDisplayStart = iAjaxStart; 4873 4874 _fnReDraw( settings ); 4875 4876 _fnProcessingDisplay( settings, false ); 4877 _fnInitComplete( settings, json ); 4878 }, settings ); 4879 } 4880 else { 4881 _fnProcessingDisplay( settings, false ); 4882 _fnInitComplete( settings ); 4883 } 4884 } 4885 } 4886 4887 4888 /** 4889 * Draw the table for the first time, adding all required features 4890 * @param {object} oSettings dataTables settings object 4891 * @param {object} [json] JSON from the server that completed the table, if using Ajax source 4892 * with client-side processing (optional) 4893 * @memberof DataTable#oApi 4894 */ 4895 function _fnInitComplete ( settings, json ) 4896 { 4897 settings._bInitComplete = true; 4898 4899 // When data was added after the initialisation (data or Ajax) we need to 4900 // calculate the column sizing 4901 if ( json || settings.oInit.aaData ) { 4902 _fnAdjustColumnSizing( settings ); 4903 } 4904 4905 _fnCallbackFire( settings, null, 'plugin-init', [settings, json] ); 4906 _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] ); 4907 } 4908 4909 4910 function _fnLengthChange ( settings, val ) 4911 { 4912 var len = parseInt( val, 10 ); 4913 settings._iDisplayLength = len; 4914 4915 _fnLengthOverflow( settings ); 4916 4917 // Fire length change event 4918 _fnCallbackFire( settings, null, 'length', [settings, len] ); 4919 } 4920 4921 4922 /** 4923 * Generate the node required for user display length changing 4924 * @param {object} settings dataTables settings object 4925 * @returns {node} Display length feature node 4926 * @memberof DataTable#oApi 4927 */ 4928 function _fnFeatureHtmlLength ( settings ) 4929 { 4930 var 4931 classes = settings.oClasses, 4932 tableId = settings.sTableId, 4933 menu = settings.aLengthMenu, 4934 d2 = Array.isArray( menu[0] ), 4935 lengths = d2 ? menu[0] : menu, 4936 language = d2 ? menu[1] : menu; 4937 4938 var select = $('<select/>', { 4939 'name': tableId+'_length', 4940 'aria-controls': tableId, 4941 'class': classes.sLengthSelect 4942 } ); 4943 4944 for ( var i=0, ien=lengths.length ; i<ien ; i++ ) { 4945 select[0][ i ] = new Option( 4946 typeof language[i] === 'number' ? 4947 settings.fnFormatNumber( language[i] ) : 4948 language[i], 4949 lengths[i] 4950 ); 4951 } 4952 4953 var div = $('<div><label/></div>').addClass( classes.sLength ); 4954 if ( ! settings.aanFeatures.l ) { 4955 div[0].id = tableId+'_length'; 4956 } 4957 4958 div.children().append( 4959 settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML ) 4960 ); 4961 4962 // Can't use `select` variable as user might provide their own and the 4963 // reference is broken by the use of outerHTML 4964 $('select', div) 4965 .val( settings._iDisplayLength ) 4966 .on( 'change.DT', function(e) { 4967 _fnLengthChange( settings, $(this).val() ); 4968 _fnDraw( settings ); 4969 } ); 4970 4971 // Update node value whenever anything changes the table's length 4972 $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { 4973 if ( settings === s ) { 4974 $('select', div).val( len ); 4975 } 4976 } ); 4977 4978 return div[0]; 4979 } 4980 4981 4982 4983 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 4984 * Note that most of the paging logic is done in 4985 * DataTable.ext.pager 4986 */ 4987 4988 /** 4989 * Generate the node required for default pagination 4990 * @param {object} oSettings dataTables settings object 4991 * @returns {node} Pagination feature node 4992 * @memberof DataTable#oApi 4993 */ 4994 function _fnFeatureHtmlPaginate ( settings ) 4995 { 4996 var 4997 type = settings.sPaginationType, 4998 plugin = DataTable.ext.pager[ type ], 4999 modern = typeof plugin === 'function', 5000 redraw = function( settings ) { 5001 _fnDraw( settings ); 5002 }, 5003 node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0], 5004 features = settings.aanFeatures; 5005 5006 if ( ! modern ) { 5007 plugin.fnInit( settings, node, redraw ); 5008 } 5009 5010 /* Add a draw callback for the pagination on first instance, to update the paging display */ 5011 if ( ! features.p ) 5012 { 5013 node.id = settings.sTableId+'_paginate'; 5014 5015 settings.aoDrawCallback.push( { 5016 "fn": function( settings ) { 5017 if ( modern ) { 5018 var 5019 start = settings._iDisplayStart, 5020 len = settings._iDisplayLength, 5021 visRecords = settings.fnRecordsDisplay(), 5022 all = len === -1, 5023 page = all ? 0 : Math.ceil( start / len ), 5024 pages = all ? 1 : Math.ceil( visRecords / len ), 5025 buttons = plugin(page, pages), 5026 i, ien; 5027 5028 for ( i=0, ien=features.p.length ; i<ien ; i++ ) { 5029 _fnRenderer( settings, 'pageButton' )( 5030 settings, features.p[i], i, buttons, page, pages 5031 ); 5032 } 5033 } 5034 else { 5035 plugin.fnUpdate( settings, redraw ); 5036 } 5037 }, 5038 "sName": "pagination" 5039 } ); 5040 } 5041 5042 return node; 5043 } 5044 5045 5046 /** 5047 * Alter the display settings to change the page 5048 * @param {object} settings DataTables settings object 5049 * @param {string|int} action Paging action to take: "first", "previous", 5050 * "next" or "last" or page number to jump to (integer) 5051 * @param [bool] redraw Automatically draw the update or not 5052 * @returns {bool} true page has changed, false - no change 5053 * @memberof DataTable#oApi 5054 */ 5055 function _fnPageChange ( settings, action, redraw ) 5056 { 5057 var 5058 start = settings._iDisplayStart, 5059 len = settings._iDisplayLength, 5060 records = settings.fnRecordsDisplay(); 5061 5062 if ( records === 0 || len === -1 ) 5063 { 5064 start = 0; 5065 } 5066 else if ( typeof action === "number" ) 5067 { 5068 start = action * len; 5069 5070 if ( start > records ) 5071 { 5072 start = 0; 5073 } 5074 } 5075 else if ( action == "first" ) 5076 { 5077 start = 0; 5078 } 5079 else if ( action == "previous" ) 5080 { 5081 start = len >= 0 ? 5082 start - len : 5083 0; 5084 5085 if ( start < 0 ) 5086 { 5087 start = 0; 5088 } 5089 } 5090 else if ( action == "next" ) 5091 { 5092 if ( start + len < records ) 5093 { 5094 start += len; 5095 } 5096 } 5097 else if ( action == "last" ) 5098 { 5099 start = Math.floor( (records-1) / len) * len; 5100 } 5101 else 5102 { 5103 _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); 5104 } 5105 5106 var changed = settings._iDisplayStart !== start; 5107 settings._iDisplayStart = start; 5108 5109 if ( changed ) { 5110 _fnCallbackFire( settings, null, 'page', [settings] ); 5111 5112 if ( redraw ) { 5113 _fnDraw( settings ); 5114 } 5115 } 5116 else { 5117 // No change event - paging was called, but no change 5118 _fnCallbackFire( settings, null, 'page-nc', [settings] ); 5119 } 5120 5121 return changed; 5122 } 5123 5124 5125 5126 /** 5127 * Generate the node required for the processing node 5128 * @param {object} settings dataTables settings object 5129 * @returns {node} Processing element 5130 * @memberof DataTable#oApi 5131 */ 5132 function _fnFeatureHtmlProcessing ( settings ) 5133 { 5134 return $('<div/>', { 5135 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null, 5136 'class': settings.oClasses.sProcessing, 5137 'role': 'status' 5138 } ) 5139 .html( settings.oLanguage.sProcessing ) 5140 .append('<div><div></div><div></div><div></div><div></div></div>') 5141 .insertBefore( settings.nTable )[0]; 5142 } 5143 5144 5145 /** 5146 * Display or hide the processing indicator 5147 * @param {object} settings dataTables settings object 5148 * @param {bool} show Show the processing indicator (true) or not (false) 5149 * @memberof DataTable#oApi 5150 */ 5151 function _fnProcessingDisplay ( settings, show ) 5152 { 5153 if ( settings.oFeatures.bProcessing ) { 5154 $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' ); 5155 } 5156 5157 _fnCallbackFire( settings, null, 'processing', [settings, show] ); 5158 } 5159 5160 /** 5161 * Add any control elements for the table - specifically scrolling 5162 * @param {object} settings dataTables settings object 5163 * @returns {node} Node to add to the DOM 5164 * @memberof DataTable#oApi 5165 */ 5166 function _fnFeatureHtmlTable ( settings ) 5167 { 5168 var table = $(settings.nTable); 5169 5170 // Scrolling from here on in 5171 var scroll = settings.oScroll; 5172 5173 if ( scroll.sX === '' && scroll.sY === '' ) { 5174 return settings.nTable; 5175 } 5176 5177 var scrollX = scroll.sX; 5178 var scrollY = scroll.sY; 5179 var classes = settings.oClasses; 5180 var caption = table.children('caption'); 5181 var captionSide = caption.length ? caption[0]._captionSide : null; 5182 var headerClone = $( table[0].cloneNode(false) ); 5183 var footerClone = $( table[0].cloneNode(false) ); 5184 var footer = table.children('tfoot'); 5185 var _div = '<div/>'; 5186 var size = function ( s ) { 5187 return !s ? null : _fnStringToCss( s ); 5188 }; 5189 5190 if ( ! footer.length ) { 5191 footer = null; 5192 } 5193 5194 /* 5195 * The HTML structure that we want to generate in this function is: 5196 * div - scroller 5197 * div - scroll head 5198 * div - scroll head inner 5199 * table - scroll head table 5200 * thead - thead 5201 * div - scroll body 5202 * table - table (master table) 5203 * thead - thead clone for sizing 5204 * tbody - tbody 5205 * div - scroll foot 5206 * div - scroll foot inner 5207 * table - scroll foot table 5208 * tfoot - tfoot 5209 */ 5210 var scroller = $( _div, { 'class': classes.sScrollWrapper } ) 5211 .append( 5212 $(_div, { 'class': classes.sScrollHead } ) 5213 .css( { 5214 overflow: 'hidden', 5215 position: 'relative', 5216 border: 0, 5217 width: scrollX ? size(scrollX) : '100%' 5218 } ) 5219 .append( 5220 $(_div, { 'class': classes.sScrollHeadInner } ) 5221 .css( { 5222 'box-sizing': 'content-box', 5223 width: scroll.sXInner || '100%' 5224 } ) 5225 .append( 5226 headerClone 5227 .removeAttr('id') 5228 .css( 'margin-left', 0 ) 5229 .append( captionSide === 'top' ? caption : null ) 5230 .append( 5231 table.children('thead') 5232 ) 5233 ) 5234 ) 5235 ) 5236 .append( 5237 $(_div, { 'class': classes.sScrollBody } ) 5238 .css( { 5239 position: 'relative', 5240 overflow: 'auto', 5241 width: size( scrollX ) 5242 } ) 5243 .append( table ) 5244 ); 5245 5246 if ( footer ) { 5247 scroller.append( 5248 $(_div, { 'class': classes.sScrollFoot } ) 5249 .css( { 5250 overflow: 'hidden', 5251 border: 0, 5252 width: scrollX ? size(scrollX) : '100%' 5253 } ) 5254 .append( 5255 $(_div, { 'class': classes.sScrollFootInner } ) 5256 .append( 5257 footerClone 5258 .removeAttr('id') 5259 .css( 'margin-left', 0 ) 5260 .append( captionSide === 'bottom' ? caption : null ) 5261 .append( 5262 table.children('tfoot') 5263 ) 5264 ) 5265 ) 5266 ); 5267 } 5268 5269 var children = scroller.children(); 5270 var scrollHead = children[0]; 5271 var scrollBody = children[1]; 5272 var scrollFoot = footer ? children[2] : null; 5273 5274 // When the body is scrolled, then we also want to scroll the headers 5275 if ( scrollX ) { 5276 $(scrollBody).on( 'scroll.DT', function (e) { 5277 var scrollLeft = this.scrollLeft; 5278 5279 scrollHead.scrollLeft = scrollLeft; 5280 5281 if ( footer ) { 5282 scrollFoot.scrollLeft = scrollLeft; 5283 } 5284 } ); 5285 } 5286 5287 $(scrollBody).css('max-height', scrollY); 5288 if (! scroll.bCollapse) { 5289 $(scrollBody).css('height', scrollY); 5290 } 5291 5292 settings.nScrollHead = scrollHead; 5293 settings.nScrollBody = scrollBody; 5294 settings.nScrollFoot = scrollFoot; 5295 5296 // On redraw - align columns 5297 settings.aoDrawCallback.push( { 5298 "fn": _fnScrollDraw, 5299 "sName": "scrolling" 5300 } ); 5301 5302 return scroller[0]; 5303 } 5304 5305 5306 5307 /** 5308 * Update the header, footer and body tables for resizing - i.e. column 5309 * alignment. 5310 * 5311 * Welcome to the most horrible function DataTables. The process that this 5312 * function follows is basically: 5313 * 1. Re-create the table inside the scrolling div 5314 * 2. Take live measurements from the DOM 5315 * 3. Apply the measurements to align the columns 5316 * 4. Clean up 5317 * 5318 * @param {object} settings dataTables settings object 5319 * @memberof DataTable#oApi 5320 */ 5321 function _fnScrollDraw ( settings ) 5322 { 5323 // Given that this is such a monster function, a lot of variables are use 5324 // to try and keep the minimised size as small as possible 5325 var 5326 scroll = settings.oScroll, 5327 scrollX = scroll.sX, 5328 scrollXInner = scroll.sXInner, 5329 scrollY = scroll.sY, 5330 barWidth = scroll.iBarWidth, 5331 divHeader = $(settings.nScrollHead), 5332 divHeaderStyle = divHeader[0].style, 5333 divHeaderInner = divHeader.children('div'), 5334 divHeaderInnerStyle = divHeaderInner[0].style, 5335 divHeaderTable = divHeaderInner.children('table'), 5336 divBodyEl = settings.nScrollBody, 5337 divBody = $(divBodyEl), 5338 divBodyStyle = divBodyEl.style, 5339 divFooter = $(settings.nScrollFoot), 5340 divFooterInner = divFooter.children('div'), 5341 divFooterTable = divFooterInner.children('table'), 5342 header = $(settings.nTHead), 5343 table = $(settings.nTable), 5344 tableEl = table[0], 5345 tableStyle = tableEl.style, 5346 footer = settings.nTFoot ? $(settings.nTFoot) : null, 5347 browser = settings.oBrowser, 5348 ie67 = browser.bScrollOversize, 5349 dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ), 5350 headerTrgEls, footerTrgEls, 5351 headerSrcEls, footerSrcEls, 5352 headerCopy, footerCopy, 5353 headerWidths=[], footerWidths=[], 5354 headerContent=[], footerContent=[], 5355 idx, correction, sanityWidth, 5356 zeroOut = function(nSizer) { 5357 var style = nSizer.style; 5358 style.paddingTop = "0"; 5359 style.paddingBottom = "0"; 5360 style.borderTopWidth = "0"; 5361 style.borderBottomWidth = "0"; 5362 style.height = 0; 5363 }; 5364 5365 // If the scrollbar visibility has changed from the last draw, we need to 5366 // adjust the column sizes as the table width will have changed to account 5367 // for the scrollbar 5368 var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; 5369 5370 if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { 5371 settings.scrollBarVis = scrollBarVis; 5372 _fnAdjustColumnSizing( settings ); 5373 return; // adjust column sizing will call this function again 5374 } 5375 else { 5376 settings.scrollBarVis = scrollBarVis; 5377 } 5378 5379 /* 5380 * 1. Re-create the table inside the scrolling div 5381 */ 5382 5383 // Remove the old minimised thead and tfoot elements in the inner table 5384 table.children('thead, tfoot').remove(); 5385 5386 if ( footer ) { 5387 footerCopy = footer.clone().prependTo( table ); 5388 footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized 5389 footerSrcEls = footerCopy.find('tr'); 5390 footerCopy.find('[id]').removeAttr('id'); 5391 } 5392 5393 // Clone the current header and footer elements and then place it into the inner table 5394 headerCopy = header.clone().prependTo( table ); 5395 headerTrgEls = header.find('tr'); // original header is in its own table 5396 headerSrcEls = headerCopy.find('tr'); 5397 headerCopy.find('th, td').removeAttr('tabindex'); 5398 headerCopy.find('[id]').removeAttr('id'); 5399 5400 5401 /* 5402 * 2. Take live measurements from the DOM - do not alter the DOM itself! 5403 */ 5404 5405 // Remove old sizing and apply the calculated column widths 5406 // Get the unique column headers in the newly created (cloned) header. We want to apply the 5407 // calculated sizes to this header 5408 if ( ! scrollX ) 5409 { 5410 divBodyStyle.width = '100%'; 5411 divHeader[0].style.width = '100%'; 5412 } 5413 5414 $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) { 5415 idx = _fnVisibleToColumnIndex( settings, i ); 5416 el.style.width = settings.aoColumns[idx].sWidth; 5417 } ); 5418 5419 if ( footer ) { 5420 _fnApplyToChildren( function(n) { 5421 n.style.width = ""; 5422 }, footerSrcEls ); 5423 } 5424 5425 // Size the table as a whole 5426 sanityWidth = table.outerWidth(); 5427 if ( scrollX === "" ) { 5428 // No x scrolling 5429 tableStyle.width = "100%"; 5430 5431 // IE7 will make the width of the table when 100% include the scrollbar 5432 // - which is shouldn't. When there is a scrollbar we need to take this 5433 // into account. 5434 if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight || 5435 divBody.css('overflow-y') == "scroll") 5436 ) { 5437 tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth); 5438 } 5439 5440 // Recalculate the sanity width 5441 sanityWidth = table.outerWidth(); 5442 } 5443 else if ( scrollXInner !== "" ) { 5444 // legacy x scroll inner has been given - use it 5445 tableStyle.width = _fnStringToCss(scrollXInner); 5446 5447 // Recalculate the sanity width 5448 sanityWidth = table.outerWidth(); 5449 } 5450 5451 // Hidden header should have zero height, so remove padding and borders. Then 5452 // set the width based on the real headers 5453 5454 // Apply all styles in one pass 5455 _fnApplyToChildren( zeroOut, headerSrcEls ); 5456 5457 // Read all widths in next pass 5458 _fnApplyToChildren( function(nSizer) { 5459 var style = window.getComputedStyle ? 5460 window.getComputedStyle(nSizer).width : 5461 _fnStringToCss( $(nSizer).width() ); 5462 5463 headerContent.push( nSizer.innerHTML ); 5464 headerWidths.push( style ); 5465 }, headerSrcEls ); 5466 5467 // Apply all widths in final pass 5468 _fnApplyToChildren( function(nToSize, i) { 5469 nToSize.style.width = headerWidths[i]; 5470 }, headerTrgEls ); 5471 5472 $(headerSrcEls).css('height', 0); 5473 5474 /* Same again with the footer if we have one */ 5475 if ( footer ) 5476 { 5477 _fnApplyToChildren( zeroOut, footerSrcEls ); 5478 5479 _fnApplyToChildren( function(nSizer) { 5480 footerContent.push( nSizer.innerHTML ); 5481 footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); 5482 }, footerSrcEls ); 5483 5484 _fnApplyToChildren( function(nToSize, i) { 5485 nToSize.style.width = footerWidths[i]; 5486 }, footerTrgEls ); 5487 5488 $(footerSrcEls).height(0); 5489 } 5490 5491 5492 /* 5493 * 3. Apply the measurements 5494 */ 5495 5496 // "Hide" the header and footer that we used for the sizing. We need to keep 5497 // the content of the cell so that the width applied to the header and body 5498 // both match, but we want to hide it completely. We want to also fix their 5499 // width to what they currently are 5500 _fnApplyToChildren( function(nSizer, i) { 5501 nSizer.innerHTML = '<div class="dataTables_sizing">'+headerContent[i]+'</div>'; 5502 nSizer.childNodes[0].style.height = "0"; 5503 nSizer.childNodes[0].style.overflow = "hidden"; 5504 nSizer.style.width = headerWidths[i]; 5505 }, headerSrcEls ); 5506 5507 if ( footer ) 5508 { 5509 _fnApplyToChildren( function(nSizer, i) { 5510 nSizer.innerHTML = '<div class="dataTables_sizing">'+footerContent[i]+'</div>'; 5511 nSizer.childNodes[0].style.height = "0"; 5512 nSizer.childNodes[0].style.overflow = "hidden"; 5513 nSizer.style.width = footerWidths[i]; 5514 }, footerSrcEls ); 5515 } 5516 5517 // Sanity check that the table is of a sensible width. If not then we are going to get 5518 // misalignment - try to prevent this by not allowing the table to shrink below its min width 5519 if ( Math.round(table.outerWidth()) < Math.round(sanityWidth) ) 5520 { 5521 // The min width depends upon if we have a vertical scrollbar visible or not */ 5522 correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || 5523 divBody.css('overflow-y') == "scroll")) ? 5524 sanityWidth+barWidth : 5525 sanityWidth; 5526 5527 // IE6/7 are a law unto themselves... 5528 if ( ie67 && (divBodyEl.scrollHeight > 5529 divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") 5530 ) { 5531 tableStyle.width = _fnStringToCss( correction-barWidth ); 5532 } 5533 5534 // And give the user a warning that we've stopped the table getting too small 5535 if ( scrollX === "" || scrollXInner !== "" ) { 5536 _fnLog( settings, 1, 'Possible column misalignment', 6 ); 5537 } 5538 } 5539 else 5540 { 5541 correction = '100%'; 5542 } 5543 5544 // Apply to the container elements 5545 divBodyStyle.width = _fnStringToCss( correction ); 5546 divHeaderStyle.width = _fnStringToCss( correction ); 5547 5548 if ( footer ) { 5549 settings.nScrollFoot.style.width = _fnStringToCss( correction ); 5550 } 5551 5552 5553 /* 5554 * 4. Clean up 5555 */ 5556 if ( ! scrollY ) { 5557 /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting 5558 * the scrollbar height from the visible display, rather than adding it on. We need to 5559 * set the height in order to sort this. Don't want to do it in any other browsers. 5560 */ 5561 if ( ie67 ) { 5562 divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth ); 5563 } 5564 } 5565 5566 /* Finally set the width's of the header and footer tables */ 5567 var iOuterWidth = table.outerWidth(); 5568 divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth ); 5569 divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth ); 5570 5571 // Figure out if there are scrollbar present - if so then we need a the header and footer to 5572 // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) 5573 var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; 5574 var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); 5575 divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px"; 5576 5577 if ( footer ) { 5578 divFooterTable[0].style.width = _fnStringToCss( iOuterWidth ); 5579 divFooterInner[0].style.width = _fnStringToCss( iOuterWidth ); 5580 divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px"; 5581 } 5582 5583 // Correct DOM ordering for colgroup - comes before the thead 5584 table.children('colgroup').insertBefore( table.children('thead') ); 5585 5586 /* Adjust the position of the header in case we loose the y-scrollbar */ 5587 divBody.trigger('scroll'); 5588 5589 // If sorting or filtering has occurred, jump the scrolling back to the top 5590 // only if we aren't holding the position 5591 if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { 5592 divBodyEl.scrollTop = 0; 5593 } 5594 } 5595 5596 5597 5598 /** 5599 * Apply a given function to the display child nodes of an element array (typically 5600 * TD children of TR rows 5601 * @param {function} fn Method to apply to the objects 5602 * @param array {nodes} an1 List of elements to look through for display children 5603 * @param array {nodes} an2 Another list (identical structure to the first) - optional 5604 * @memberof DataTable#oApi 5605 */ 5606 function _fnApplyToChildren( fn, an1, an2 ) 5607 { 5608 var index=0, i=0, iLen=an1.length; 5609 var nNode1, nNode2; 5610 5611 while ( i < iLen ) { 5612 nNode1 = an1[i].firstChild; 5613 nNode2 = an2 ? an2[i].firstChild : null; 5614 5615 while ( nNode1 ) { 5616 if ( nNode1.nodeType === 1 ) { 5617 if ( an2 ) { 5618 fn( nNode1, nNode2, index ); 5619 } 5620 else { 5621 fn( nNode1, index ); 5622 } 5623 5624 index++; 5625 } 5626 5627 nNode1 = nNode1.nextSibling; 5628 nNode2 = an2 ? nNode2.nextSibling : null; 5629 } 5630 5631 i++; 5632 } 5633 } 5634 5635 5636 5637 var __re_html_remove = /<.*?>/g; 5638 5639 5640 /** 5641 * Calculate the width of columns for the table 5642 * @param {object} oSettings dataTables settings object 5643 * @memberof DataTable#oApi 5644 */ 5645 function _fnCalculateColumnWidths ( oSettings ) 5646 { 5647 var 5648 table = oSettings.nTable, 5649 columns = oSettings.aoColumns, 5650 scroll = oSettings.oScroll, 5651 scrollY = scroll.sY, 5652 scrollX = scroll.sX, 5653 scrollXInner = scroll.sXInner, 5654 columnCount = columns.length, 5655 visibleColumns = _fnGetColumns( oSettings, 'bVisible' ), 5656 headerCells = $('th', oSettings.nTHead), 5657 tableWidthAttr = table.getAttribute('width'), // from DOM element 5658 tableContainer = table.parentNode, 5659 userInputs = false, 5660 i, column, columnIdx, width, outerWidth, 5661 browser = oSettings.oBrowser, 5662 ie67 = browser.bScrollOversize; 5663 5664 var styleWidth = table.style.width; 5665 if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { 5666 tableWidthAttr = styleWidth; 5667 } 5668 5669 /* Convert any user input sizes into pixel sizes */ 5670 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5671 column = columns[ visibleColumns[i] ]; 5672 5673 if ( column.sWidth !== null ) { 5674 column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer ); 5675 5676 userInputs = true; 5677 } 5678 } 5679 5680 /* If the number of columns in the DOM equals the number that we have to 5681 * process in DataTables, then we can use the offsets that are created by 5682 * the web- browser. No custom sizes can be set in order for this to happen, 5683 * nor scrolling used 5684 */ 5685 if ( ie67 || ! userInputs && ! scrollX && ! scrollY && 5686 columnCount == _fnVisbleColumns( oSettings ) && 5687 columnCount == headerCells.length 5688 ) { 5689 for ( i=0 ; i<columnCount ; i++ ) { 5690 var colIdx = _fnVisibleToColumnIndex( oSettings, i ); 5691 5692 if ( colIdx !== null ) { 5693 columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() ); 5694 } 5695 } 5696 } 5697 else 5698 { 5699 // Otherwise construct a single row, worst case, table with the widest 5700 // node in the data, assign any user defined widths, then insert it into 5701 // the DOM and allow the browser to do all the hard work of calculating 5702 // table widths 5703 var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table 5704 .css( 'visibility', 'hidden' ) 5705 .removeAttr( 'id' ); 5706 5707 // Clean up the table body 5708 tmpTable.find('tbody tr').remove(); 5709 var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); 5710 5711 // Clone the table header and footer - we can't use the header / footer 5712 // from the cloned table, since if scrolling is active, the table's 5713 // real header and footer are contained in different table tags 5714 tmpTable.find('thead, tfoot').remove(); 5715 tmpTable 5716 .append( $(oSettings.nTHead).clone() ) 5717 .append( $(oSettings.nTFoot).clone() ); 5718 5719 // Remove any assigned widths from the footer (from scrolling) 5720 tmpTable.find('tfoot th, tfoot td').css('width', ''); 5721 5722 // Apply custom sizing to the cloned header 5723 headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] ); 5724 5725 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5726 column = columns[ visibleColumns[i] ]; 5727 5728 headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? 5729 _fnStringToCss( column.sWidthOrig ) : 5730 ''; 5731 5732 // For scrollX we need to force the column width otherwise the 5733 // browser will collapse it. If this width is smaller than the 5734 // width the column requires, then it will have no effect 5735 if ( column.sWidthOrig && scrollX ) { 5736 $( headerCells[i] ).append( $('<div/>').css( { 5737 width: column.sWidthOrig, 5738 margin: 0, 5739 padding: 0, 5740 border: 0, 5741 height: 1 5742 } ) ); 5743 } 5744 } 5745 5746 // Find the widest cell for each column and put it into the table 5747 if ( oSettings.aoData.length ) { 5748 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5749 columnIdx = visibleColumns[i]; 5750 column = columns[ columnIdx ]; 5751 5752 $( _fnGetWidestNode( oSettings, columnIdx ) ) 5753 .clone( false ) 5754 .append( column.sContentPadding ) 5755 .appendTo( tr ); 5756 } 5757 } 5758 5759 // Tidy the temporary table - remove name attributes so there aren't 5760 // duplicated in the dom (radio elements for example) 5761 $('[name]', tmpTable).removeAttr('name'); 5762 5763 // Table has been built, attach to the document so we can work with it. 5764 // A holding element is used, positioned at the top of the container 5765 // with minimal height, so it has no effect on if the container scrolls 5766 // or not. Otherwise it might trigger scrolling when it actually isn't 5767 // needed 5768 var holder = $('<div/>').css( scrollX || scrollY ? 5769 { 5770 position: 'absolute', 5771 top: 0, 5772 left: 0, 5773 height: 1, 5774 right: 0, 5775 overflow: 'hidden' 5776 } : 5777 {} 5778 ) 5779 .append( tmpTable ) 5780 .appendTo( tableContainer ); 5781 5782 // When scrolling (X or Y) we want to set the width of the table as 5783 // appropriate. However, when not scrolling leave the table width as it 5784 // is. This results in slightly different, but I think correct behaviour 5785 if ( scrollX && scrollXInner ) { 5786 tmpTable.width( scrollXInner ); 5787 } 5788 else if ( scrollX ) { 5789 tmpTable.css( 'width', 'auto' ); 5790 tmpTable.removeAttr('width'); 5791 5792 // If there is no width attribute or style, then allow the table to 5793 // collapse 5794 if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) { 5795 tmpTable.width( tableContainer.clientWidth ); 5796 } 5797 } 5798 else if ( scrollY ) { 5799 tmpTable.width( tableContainer.clientWidth ); 5800 } 5801 else if ( tableWidthAttr ) { 5802 tmpTable.width( tableWidthAttr ); 5803 } 5804 5805 // Get the width of each column in the constructed table - we need to 5806 // know the inner width (so it can be assigned to the other table's 5807 // cells) and the outer width so we can calculate the full width of the 5808 // table. This is safe since DataTables requires a unique cell for each 5809 // column, but if ever a header can span multiple columns, this will 5810 // need to be modified. 5811 var total = 0; 5812 for ( i=0 ; i<visibleColumns.length ; i++ ) { 5813 var cell = $(headerCells[i]); 5814 var border = cell.outerWidth() - cell.width(); 5815 5816 // Use getBounding... where possible (not IE8-) because it can give 5817 // sub-pixel accuracy, which we then want to round up! 5818 var bounding = browser.bBounding ? 5819 Math.ceil( headerCells[i].getBoundingClientRect().width ) : 5820 cell.outerWidth(); 5821 5822 // Total is tracked to remove any sub-pixel errors as the outerWidth 5823 // of the table might not equal the total given here (IE!). 5824 total += bounding; 5825 5826 // Width for each column to use 5827 columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border ); 5828 } 5829 5830 table.style.width = _fnStringToCss( total ); 5831 5832 // Finished with the table - ditch it 5833 holder.remove(); 5834 } 5835 5836 // If there is a width attr, we want to attach an event listener which 5837 // allows the table sizing to automatically adjust when the window is 5838 // resized. Use the width attr rather than CSS, since we can't know if the 5839 // CSS is a relative value or absolute - DOM read is always px. 5840 if ( tableWidthAttr ) { 5841 table.style.width = _fnStringToCss( tableWidthAttr ); 5842 } 5843 5844 if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) { 5845 var bindResize = function () { 5846 $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () { 5847 _fnAdjustColumnSizing( oSettings ); 5848 } ) ); 5849 }; 5850 5851 // IE6/7 will crash if we bind a resize event handler on page load. 5852 // To be removed in 1.11 which drops IE6/7 support 5853 if ( ie67 ) { 5854 setTimeout( bindResize, 1000 ); 5855 } 5856 else { 5857 bindResize(); 5858 } 5859 5860 oSettings._reszEvt = true; 5861 } 5862 } 5863 5864 5865 /** 5866 * Throttle the calls to a function. Arguments and context are maintained for 5867 * the throttled function 5868 * @param {function} fn Function to be called 5869 * @param {int} [freq=200] call frequency in mS 5870 * @returns {function} wrapped function 5871 * @memberof DataTable#oApi 5872 */ 5873 var _fnThrottle = DataTable.util.throttle; 5874 5875 5876 /** 5877 * Convert a CSS unit width to pixels (e.g. 2em) 5878 * @param {string} width width to be converted 5879 * @param {node} parent parent to get the with for (required for relative widths) - optional 5880 * @returns {int} width in pixels 5881 * @memberof DataTable#oApi 5882 */ 5883 function _fnConvertToWidth ( width, parent ) 5884 { 5885 if ( ! width ) { 5886 return 0; 5887 } 5888 5889 var n = $('<div/>') 5890 .css( 'width', _fnStringToCss( width ) ) 5891 .appendTo( parent || document.body ); 5892 5893 var val = n[0].offsetWidth; 5894 n.remove(); 5895 5896 return val; 5897 } 5898 5899 5900 /** 5901 * Get the widest node 5902 * @param {object} settings dataTables settings object 5903 * @param {int} colIdx column of interest 5904 * @returns {node} widest table node 5905 * @memberof DataTable#oApi 5906 */ 5907 function _fnGetWidestNode( settings, colIdx ) 5908 { 5909 var idx = _fnGetMaxLenString( settings, colIdx ); 5910 if ( idx < 0 ) { 5911 return null; 5912 } 5913 5914 var data = settings.aoData[ idx ]; 5915 return ! data.nTr ? // Might not have been created when deferred rendering 5916 $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] : 5917 data.anCells[ colIdx ]; 5918 } 5919 5920 5921 /** 5922 * Get the maximum strlen for each data column 5923 * @param {object} settings dataTables settings object 5924 * @param {int} colIdx column of interest 5925 * @returns {string} max string length for each column 5926 * @memberof DataTable#oApi 5927 */ 5928 function _fnGetMaxLenString( settings, colIdx ) 5929 { 5930 var s, max=-1, maxIdx = -1; 5931 5932 for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 5933 s = _fnGetCellData( settings, i, colIdx, 'display' )+''; 5934 s = s.replace( __re_html_remove, '' ); 5935 s = s.replace( / /g, ' ' ); 5936 5937 if ( s.length > max ) { 5938 max = s.length; 5939 maxIdx = i; 5940 } 5941 } 5942 5943 return maxIdx; 5944 } 5945 5946 5947 /** 5948 * Append a CSS unit (only if required) to a string 5949 * @param {string} value to css-ify 5950 * @returns {string} value with css unit 5951 * @memberof DataTable#oApi 5952 */ 5953 function _fnStringToCss( s ) 5954 { 5955 if ( s === null ) { 5956 return '0px'; 5957 } 5958 5959 if ( typeof s == 'number' ) { 5960 return s < 0 ? 5961 '0px' : 5962 s+'px'; 5963 } 5964 5965 // Check it has a unit character already 5966 return s.match(/\d$/) ? 5967 s+'px' : 5968 s; 5969 } 5970 5971 5972 5973 function _fnSortFlatten ( settings ) 5974 { 5975 var 5976 i, iLen, k, kLen, 5977 aSort = [], 5978 aiOrig = [], 5979 aoColumns = settings.aoColumns, 5980 aDataSort, iCol, sType, srcCol, 5981 fixed = settings.aaSortingFixed, 5982 fixedObj = $.isPlainObject( fixed ), 5983 nestedSort = [], 5984 add = function ( a ) { 5985 if ( a.length && ! Array.isArray( a[0] ) ) { 5986 // 1D array 5987 nestedSort.push( a ); 5988 } 5989 else { 5990 // 2D array 5991 $.merge( nestedSort, a ); 5992 } 5993 }; 5994 5995 // Build the sort array, with pre-fix and post-fix options if they have been 5996 // specified 5997 if ( Array.isArray( fixed ) ) { 5998 add( fixed ); 5999 } 6000 6001 if ( fixedObj && fixed.pre ) { 6002 add( fixed.pre ); 6003 } 6004 6005 add( settings.aaSorting ); 6006 6007 if (fixedObj && fixed.post ) { 6008 add( fixed.post ); 6009 } 6010 6011 for ( i=0 ; i<nestedSort.length ; i++ ) 6012 { 6013 srcCol = nestedSort[i][0]; 6014 aDataSort = aoColumns[ srcCol ].aDataSort; 6015 6016 for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) 6017 { 6018 iCol = aDataSort[k]; 6019 sType = aoColumns[ iCol ].sType || 'string'; 6020 6021 if ( nestedSort[i]._idx === undefined ) { 6022 nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting ); 6023 } 6024 6025 aSort.push( { 6026 src: srcCol, 6027 col: iCol, 6028 dir: nestedSort[i][1], 6029 index: nestedSort[i]._idx, 6030 type: sType, 6031 formatter: DataTable.ext.type.order[ sType+"-pre" ] 6032 } ); 6033 } 6034 } 6035 6036 return aSort; 6037 } 6038 6039 /** 6040 * Change the order of the table 6041 * @param {object} oSettings dataTables settings object 6042 * @memberof DataTable#oApi 6043 * @todo This really needs split up! 6044 */ 6045 function _fnSort ( oSettings ) 6046 { 6047 var 6048 i, ien, iLen, j, jLen, k, kLen, 6049 sDataType, nTh, 6050 aiOrig = [], 6051 oExtSort = DataTable.ext.type.order, 6052 aoData = oSettings.aoData, 6053 aoColumns = oSettings.aoColumns, 6054 aDataSort, data, iCol, sType, oSort, 6055 formatters = 0, 6056 sortCol, 6057 displayMaster = oSettings.aiDisplayMaster, 6058 aSort; 6059 6060 // Resolve any column types that are unknown due to addition or invalidation 6061 // @todo Can this be moved into a 'data-ready' handler which is called when 6062 // data is going to be used in the table? 6063 _fnColumnTypes( oSettings ); 6064 6065 aSort = _fnSortFlatten( oSettings ); 6066 6067 for ( i=0, ien=aSort.length ; i<ien ; i++ ) { 6068 sortCol = aSort[i]; 6069 6070 // Track if we can use the fast sort algorithm 6071 if ( sortCol.formatter ) { 6072 formatters++; 6073 } 6074 6075 // Load the data needed for the sort, for each cell 6076 _fnSortData( oSettings, sortCol.col ); 6077 } 6078 6079 /* No sorting required if server-side or no sorting array */ 6080 if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) 6081 { 6082 // Create a value - key array of the current row positions such that we can use their 6083 // current position during the sort, if values match, in order to perform stable sorting 6084 for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { 6085 aiOrig[ displayMaster[i] ] = i; 6086 } 6087 6088 /* Do the sort - here we want multi-column sorting based on a given data source (column) 6089 * and sorting function (from oSort) in a certain direction. It's reasonably complex to 6090 * follow on it's own, but this is what we want (example two column sorting): 6091 * fnLocalSorting = function(a,b){ 6092 * var iTest; 6093 * iTest = oSort['string-asc']('data11', 'data12'); 6094 * if (iTest !== 0) 6095 * return iTest; 6096 * iTest = oSort['numeric-desc']('data21', 'data22'); 6097 * if (iTest !== 0) 6098 * return iTest; 6099 * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); 6100 * } 6101 * Basically we have a test for each sorting column, if the data in that column is equal, 6102 * test the next column. If all columns match, then we use a numeric sort on the row 6103 * positions in the original data array to provide a stable sort. 6104 * 6105 * Note - I know it seems excessive to have two sorting methods, but the first is around 6106 * 15% faster, so the second is only maintained for backwards compatibility with sorting 6107 * methods which do not have a pre-sort formatting function. 6108 */ 6109 if ( formatters === aSort.length ) { 6110 // All sort types have formatting functions 6111 displayMaster.sort( function ( a, b ) { 6112 var 6113 x, y, k, test, sort, 6114 len=aSort.length, 6115 dataA = aoData[a]._aSortData, 6116 dataB = aoData[b]._aSortData; 6117 6118 for ( k=0 ; k<len ; k++ ) { 6119 sort = aSort[k]; 6120 6121 x = dataA[ sort.col ]; 6122 y = dataB[ sort.col ]; 6123 6124 test = x<y ? -1 : x>y ? 1 : 0; 6125 if ( test !== 0 ) { 6126 return sort.dir === 'asc' ? test : -test; 6127 } 6128 } 6129 6130 x = aiOrig[a]; 6131 y = aiOrig[b]; 6132 return x<y ? -1 : x>y ? 1 : 0; 6133 } ); 6134 } 6135 else { 6136 // Depreciated - remove in 1.11 (providing a plug-in option) 6137 // Not all sort types have formatting methods, so we have to call their sorting 6138 // methods. 6139 displayMaster.sort( function ( a, b ) { 6140 var 6141 x, y, k, l, test, sort, fn, 6142 len=aSort.length, 6143 dataA = aoData[a]._aSortData, 6144 dataB = aoData[b]._aSortData; 6145 6146 for ( k=0 ; k<len ; k++ ) { 6147 sort = aSort[k]; 6148 6149 x = dataA[ sort.col ]; 6150 y = dataB[ sort.col ]; 6151 6152 fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ]; 6153 test = fn( x, y ); 6154 if ( test !== 0 ) { 6155 return test; 6156 } 6157 } 6158 6159 x = aiOrig[a]; 6160 y = aiOrig[b]; 6161 return x<y ? -1 : x>y ? 1 : 0; 6162 } ); 6163 } 6164 } 6165 6166 /* Tell the draw function that we have sorted the data */ 6167 oSettings.bSorted = true; 6168 } 6169 6170 6171 function _fnSortAria ( settings ) 6172 { 6173 var label; 6174 var nextSort; 6175 var columns = settings.aoColumns; 6176 var aSort = _fnSortFlatten( settings ); 6177 var oAria = settings.oLanguage.oAria; 6178 6179 // ARIA attributes - need to loop all columns, to update all (removing old 6180 // attributes as needed) 6181 for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) 6182 { 6183 var col = columns[i]; 6184 var asSorting = col.asSorting; 6185 var sTitle = col.ariaTitle || col.sTitle.replace( /<.*?>/g, "" ); 6186 var th = col.nTh; 6187 6188 // IE7 is throwing an error when setting these properties with jQuery's 6189 // attr() and removeAttr() methods... 6190 th.removeAttribute('aria-sort'); 6191 6192 /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ 6193 if ( col.bSortable ) { 6194 if ( aSort.length > 0 && aSort[0].col == i ) { 6195 th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); 6196 nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0]; 6197 } 6198 else { 6199 nextSort = asSorting[0]; 6200 } 6201 6202 label = sTitle + ( nextSort === "asc" ? 6203 oAria.sSortAscending : 6204 oAria.sSortDescending 6205 ); 6206 } 6207 else { 6208 label = sTitle; 6209 } 6210 6211 th.setAttribute('aria-label', label); 6212 } 6213 } 6214 6215 6216 /** 6217 * Function to run on user sort request 6218 * @param {object} settings dataTables settings object 6219 * @param {node} attachTo node to attach the handler to 6220 * @param {int} colIdx column sorting index 6221 * @param {boolean} [append=false] Append the requested sort to the existing 6222 * sort if true (i.e. multi-column sort) 6223 * @param {function} [callback] callback function 6224 * @memberof DataTable#oApi 6225 */ 6226 function _fnSortListener ( settings, colIdx, append, callback ) 6227 { 6228 var col = settings.aoColumns[ colIdx ]; 6229 var sorting = settings.aaSorting; 6230 var asSorting = col.asSorting; 6231 var nextSortIdx; 6232 var next = function ( a, overflow ) { 6233 var idx = a._idx; 6234 if ( idx === undefined ) { 6235 idx = $.inArray( a[1], asSorting ); 6236 } 6237 6238 return idx+1 < asSorting.length ? 6239 idx+1 : 6240 overflow ? 6241 null : 6242 0; 6243 }; 6244 6245 // Convert to 2D array if needed 6246 if ( typeof sorting[0] === 'number' ) { 6247 sorting = settings.aaSorting = [ sorting ]; 6248 } 6249 6250 // If appending the sort then we are multi-column sorting 6251 if ( append && settings.oFeatures.bSortMulti ) { 6252 // Are we already doing some kind of sort on this column? 6253 var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); 6254 6255 if ( sortIdx !== -1 ) { 6256 // Yes, modify the sort 6257 nextSortIdx = next( sorting[sortIdx], true ); 6258 6259 if ( nextSortIdx === null && sorting.length === 1 ) { 6260 nextSortIdx = 0; // can't remove sorting completely 6261 } 6262 6263 if ( nextSortIdx === null ) { 6264 sorting.splice( sortIdx, 1 ); 6265 } 6266 else { 6267 sorting[sortIdx][1] = asSorting[ nextSortIdx ]; 6268 sorting[sortIdx]._idx = nextSortIdx; 6269 } 6270 } 6271 else { 6272 // No sort on this column yet 6273 sorting.push( [ colIdx, asSorting[0], 0 ] ); 6274 sorting[sorting.length-1]._idx = 0; 6275 } 6276 } 6277 else if ( sorting.length && sorting[0][0] == colIdx ) { 6278 // Single column - already sorting on this column, modify the sort 6279 nextSortIdx = next( sorting[0] ); 6280 6281 sorting.length = 1; 6282 sorting[0][1] = asSorting[ nextSortIdx ]; 6283 sorting[0]._idx = nextSortIdx; 6284 } 6285 else { 6286 // Single column - sort only on this column 6287 sorting.length = 0; 6288 sorting.push( [ colIdx, asSorting[0] ] ); 6289 sorting[0]._idx = 0; 6290 } 6291 6292 // Run the sort by calling a full redraw 6293 _fnReDraw( settings ); 6294 6295 // callback used for async user interaction 6296 if ( typeof callback == 'function' ) { 6297 callback( settings ); 6298 } 6299 } 6300 6301 6302 /** 6303 * Attach a sort handler (click) to a node 6304 * @param {object} settings dataTables settings object 6305 * @param {node} attachTo node to attach the handler to 6306 * @param {int} colIdx column sorting index 6307 * @param {function} [callback] callback function 6308 * @memberof DataTable#oApi 6309 */ 6310 function _fnSortAttachListener ( settings, attachTo, colIdx, callback ) 6311 { 6312 var col = settings.aoColumns[ colIdx ]; 6313 6314 _fnBindAction( attachTo, {}, function (e) { 6315 /* If the column is not sortable - don't to anything */ 6316 if ( col.bSortable === false ) { 6317 return; 6318 } 6319 6320 // If processing is enabled use a timeout to allow the processing 6321 // display to be shown - otherwise to it synchronously 6322 if ( settings.oFeatures.bProcessing ) { 6323 _fnProcessingDisplay( settings, true ); 6324 6325 setTimeout( function() { 6326 _fnSortListener( settings, colIdx, e.shiftKey, callback ); 6327 6328 // In server-side processing, the draw callback will remove the 6329 // processing display 6330 if ( _fnDataSource( settings ) !== 'ssp' ) { 6331 _fnProcessingDisplay( settings, false ); 6332 } 6333 }, 0 ); 6334 } 6335 else { 6336 _fnSortListener( settings, colIdx, e.shiftKey, callback ); 6337 } 6338 } ); 6339 } 6340 6341 6342 /** 6343 * Set the sorting classes on table's body, Note: it is safe to call this function 6344 * when bSort and bSortClasses are false 6345 * @param {object} oSettings dataTables settings object 6346 * @memberof DataTable#oApi 6347 */ 6348 function _fnSortingClasses( settings ) 6349 { 6350 var oldSort = settings.aLastSort; 6351 var sortClass = settings.oClasses.sSortColumn; 6352 var sort = _fnSortFlatten( settings ); 6353 var features = settings.oFeatures; 6354 var i, ien, colIdx; 6355 6356 if ( features.bSort && features.bSortClasses ) { 6357 // Remove old sorting classes 6358 for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { 6359 colIdx = oldSort[i].src; 6360 6361 // Remove column sorting 6362 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6363 .removeClass( sortClass + (i<2 ? i+1 : 3) ); 6364 } 6365 6366 // Add new column sorting 6367 for ( i=0, ien=sort.length ; i<ien ; i++ ) { 6368 colIdx = sort[i].src; 6369 6370 $( _pluck( settings.aoData, 'anCells', colIdx ) ) 6371 .addClass( sortClass + (i<2 ? i+1 : 3) ); 6372 } 6373 } 6374 6375 settings.aLastSort = sort; 6376 } 6377 6378 6379 // Get the data to sort a column, be it from cache, fresh (populating the 6380 // cache), or from a sort formatter 6381 function _fnSortData( settings, idx ) 6382 { 6383 // Custom sorting function - provided by the sort data type 6384 var column = settings.aoColumns[ idx ]; 6385 var customSort = DataTable.ext.order[ column.sSortDataType ]; 6386 var customData; 6387 6388 if ( customSort ) { 6389 customData = customSort.call( settings.oInstance, settings, idx, 6390 _fnColumnIndexToVisible( settings, idx ) 6391 ); 6392 } 6393 6394 // Use / populate cache 6395 var row, cellData; 6396 var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; 6397 6398 for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 6399 row = settings.aoData[i]; 6400 6401 if ( ! row._aSortData ) { 6402 row._aSortData = []; 6403 } 6404 6405 if ( ! row._aSortData[idx] || customSort ) { 6406 cellData = customSort ? 6407 customData[i] : // If there was a custom sort function, use data from there 6408 _fnGetCellData( settings, i, idx, 'sort' ); 6409 6410 row._aSortData[ idx ] = formatter ? 6411 formatter( cellData ) : 6412 cellData; 6413 } 6414 } 6415 } 6416 6417 6418 6419 /** 6420 * Save the state of a table 6421 * @param {object} oSettings dataTables settings object 6422 * @memberof DataTable#oApi 6423 */ 6424 function _fnSaveState ( settings ) 6425 { 6426 if (settings._bLoadingState) { 6427 return; 6428 } 6429 6430 /* Store the interesting variables */ 6431 var state = { 6432 time: +new Date(), 6433 start: settings._iDisplayStart, 6434 length: settings._iDisplayLength, 6435 order: $.extend( true, [], settings.aaSorting ), 6436 search: _fnSearchToCamel( settings.oPreviousSearch ), 6437 columns: $.map( settings.aoColumns, function ( col, i ) { 6438 return { 6439 visible: col.bVisible, 6440 search: _fnSearchToCamel( settings.aoPreSearchCols[i] ) 6441 }; 6442 } ) 6443 }; 6444 6445 settings.oSavedState = state; 6446 _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); 6447 6448 if ( settings.oFeatures.bStateSave && !settings.bDestroying ) 6449 { 6450 settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); 6451 } 6452 } 6453 6454 6455 /** 6456 * Attempt to load a saved table state 6457 * @param {object} oSettings dataTables settings object 6458 * @param {object} oInit DataTables init object so we can override settings 6459 * @param {function} callback Callback to execute when the state has been loaded 6460 * @memberof DataTable#oApi 6461 */ 6462 function _fnLoadState ( settings, oInit, callback ) 6463 { 6464 if ( ! settings.oFeatures.bStateSave ) { 6465 callback(); 6466 return; 6467 } 6468 6469 var loaded = function(state) { 6470 _fnImplementState(settings, state, callback); 6471 } 6472 6473 var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); 6474 6475 if ( state !== undefined ) { 6476 _fnImplementState( settings, state, callback ); 6477 } 6478 // otherwise, wait for the loaded callback to be executed 6479 6480 return true; 6481 } 6482 6483 function _fnImplementState ( settings, s, callback) { 6484 var i, ien; 6485 var columns = settings.aoColumns; 6486 settings._bLoadingState = true; 6487 6488 // When StateRestore was introduced the state could now be implemented at any time 6489 // Not just initialisation. To do this an api instance is required in some places 6490 var api = settings._bInitComplete ? new DataTable.Api(settings) : null; 6491 6492 if ( ! s || ! s.time ) { 6493 settings._bLoadingState = false; 6494 callback(); 6495 return; 6496 } 6497 6498 // Allow custom and plug-in manipulation functions to alter the saved data set and 6499 // cancelling of loading by returning false 6500 var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); 6501 if ( $.inArray( false, abStateLoad ) !== -1 ) { 6502 settings._bLoadingState = false; 6503 callback(); 6504 return; 6505 } 6506 6507 // Reject old data 6508 var duration = settings.iStateDuration; 6509 if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { 6510 settings._bLoadingState = false; 6511 callback(); 6512 return; 6513 } 6514 6515 // Number of columns have changed - all bets are off, no restore of settings 6516 if ( s.columns && columns.length !== s.columns.length ) { 6517 settings._bLoadingState = false; 6518 callback(); 6519 return; 6520 } 6521 6522 // Store the saved state so it might be accessed at any time 6523 settings.oLoadedState = $.extend( true, {}, s ); 6524 6525 // Page Length 6526 if ( s.length !== undefined ) { 6527 // If already initialised just set the value directly so that the select element is also updated 6528 if (api) { 6529 api.page.len(s.length) 6530 } 6531 else { 6532 settings._iDisplayLength = s.length; 6533 } 6534 } 6535 6536 // Restore key features - todo - for 1.11 this needs to be done by 6537 // subscribed events 6538 if ( s.start !== undefined ) { 6539 if(api === null) { 6540 settings._iDisplayStart = s.start; 6541 settings.iInitDisplayStart = s.start; 6542 } 6543 else { 6544 _fnPageChange(settings, s.start/settings._iDisplayLength); 6545 } 6546 } 6547 6548 // Order 6549 if ( s.order !== undefined ) { 6550 settings.aaSorting = []; 6551 $.each( s.order, function ( i, col ) { 6552 settings.aaSorting.push( col[0] >= columns.length ? 6553 [ 0, col[1] ] : 6554 col 6555 ); 6556 } ); 6557 } 6558 6559 // Search 6560 if ( s.search !== undefined ) { 6561 $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) ); 6562 } 6563 6564 // Columns 6565 if ( s.columns ) { 6566 for ( i=0, ien=s.columns.length ; i<ien ; i++ ) { 6567 var col = s.columns[i]; 6568 6569 // Visibility 6570 if ( col.visible !== undefined ) { 6571 // If the api is defined, the table has been initialised so we need to use it rather than internal settings 6572 if (api) { 6573 // Don't redraw the columns on every iteration of this loop, we will do this at the end instead 6574 api.column(i).visible(col.visible, false); 6575 } 6576 else { 6577 columns[i].bVisible = col.visible; 6578 } 6579 } 6580 6581 // Search 6582 if ( col.search !== undefined ) { 6583 $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) ); 6584 } 6585 } 6586 6587 // If the api is defined then we need to adjust the columns once the visibility has been changed 6588 if (api) { 6589 api.columns.adjust(); 6590 } 6591 } 6592 6593 settings._bLoadingState = false; 6594 _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); 6595 callback(); 6596 }; 6597 6598 6599 /** 6600 * Return the settings object for a particular table 6601 * @param {node} table table we are using as a dataTable 6602 * @returns {object} Settings object - or null if not found 6603 * @memberof DataTable#oApi 6604 */ 6605 function _fnSettingsFromNode ( table ) 6606 { 6607 var settings = DataTable.settings; 6608 var idx = $.inArray( table, _pluck( settings, 'nTable' ) ); 6609 6610 return idx !== -1 ? 6611 settings[ idx ] : 6612 null; 6613 } 6614 6615 6616 /** 6617 * Log an error message 6618 * @param {object} settings dataTables settings object 6619 * @param {int} level log error messages, or display them to the user 6620 * @param {string} msg error message 6621 * @param {int} tn Technical note id to get more information about the error. 6622 * @memberof DataTable#oApi 6623 */ 6624 function _fnLog( settings, level, msg, tn ) 6625 { 6626 msg = 'DataTables warning: '+ 6627 (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; 6628 6629 if ( tn ) { 6630 msg += '. For more information about this error, please see '+ 6631 'http://datatables.net/tn/'+tn; 6632 } 6633 6634 if ( ! level ) { 6635 // Backwards compatibility pre 1.10 6636 var ext = DataTable.ext; 6637 var type = ext.sErrMode || ext.errMode; 6638 6639 if ( settings ) { 6640 _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] ); 6641 } 6642 6643 if ( type == 'alert' ) { 6644 alert( msg ); 6645 } 6646 else if ( type == 'throw' ) { 6647 throw new Error(msg); 6648 } 6649 else if ( typeof type == 'function' ) { 6650 type( settings, tn, msg ); 6651 } 6652 } 6653 else if ( window.console && console.log ) { 6654 console.log( msg ); 6655 } 6656 } 6657 6658 6659 /** 6660 * See if a property is defined on one object, if so assign it to the other object 6661 * @param {object} ret target object 6662 * @param {object} src source object 6663 * @param {string} name property 6664 * @param {string} [mappedName] name to map too - optional, name used if not given 6665 * @memberof DataTable#oApi 6666 */ 6667 function _fnMap( ret, src, name, mappedName ) 6668 { 6669 if ( Array.isArray( name ) ) { 6670 $.each( name, function (i, val) { 6671 if ( Array.isArray( val ) ) { 6672 _fnMap( ret, src, val[0], val[1] ); 6673 } 6674 else { 6675 _fnMap( ret, src, val ); 6676 } 6677 } ); 6678 6679 return; 6680 } 6681 6682 if ( mappedName === undefined ) { 6683 mappedName = name; 6684 } 6685 6686 if ( src[name] !== undefined ) { 6687 ret[mappedName] = src[name]; 6688 } 6689 } 6690 6691 6692 /** 6693 * Extend objects - very similar to jQuery.extend, but deep copy objects, and 6694 * shallow copy arrays. The reason we need to do this, is that we don't want to 6695 * deep copy array init values (such as aaSorting) since the dev wouldn't be 6696 * able to override them, but we do want to deep copy arrays. 6697 * @param {object} out Object to extend 6698 * @param {object} extender Object from which the properties will be applied to 6699 * out 6700 * @param {boolean} breakRefs If true, then arrays will be sliced to take an 6701 * independent copy with the exception of the `data` or `aaData` parameters 6702 * if they are present. This is so you can pass in a collection to 6703 * DataTables and have that used as your data source without breaking the 6704 * references 6705 * @returns {object} out Reference, just for convenience - out === the return. 6706 * @memberof DataTable#oApi 6707 * @todo This doesn't take account of arrays inside the deep copied objects. 6708 */ 6709 function _fnExtend( out, extender, breakRefs ) 6710 { 6711 var val; 6712 6713 for ( var prop in extender ) { 6714 if ( extender.hasOwnProperty(prop) ) { 6715 val = extender[prop]; 6716 6717 if ( $.isPlainObject( val ) ) { 6718 if ( ! $.isPlainObject( out[prop] ) ) { 6719 out[prop] = {}; 6720 } 6721 $.extend( true, out[prop], val ); 6722 } 6723 else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) { 6724 out[prop] = val.slice(); 6725 } 6726 else { 6727 out[prop] = val; 6728 } 6729 } 6730 } 6731 6732 return out; 6733 } 6734 6735 6736 /** 6737 * Bind an event handers to allow a click or return key to activate the callback. 6738 * This is good for accessibility since a return on the keyboard will have the 6739 * same effect as a click, if the element has focus. 6740 * @param {element} n Element to bind the action to 6741 * @param {object} oData Data object to pass to the triggered function 6742 * @param {function} fn Callback function for when the event is triggered 6743 * @memberof DataTable#oApi 6744 */ 6745 function _fnBindAction( n, oData, fn ) 6746 { 6747 $(n) 6748 .on( 'click.DT', oData, function (e) { 6749 $(n).trigger('blur'); // Remove focus outline for mouse users 6750 fn(e); 6751 } ) 6752 .on( 'keypress.DT', oData, function (e){ 6753 if ( e.which === 13 ) { 6754 e.preventDefault(); 6755 fn(e); 6756 } 6757 } ) 6758 .on( 'selectstart.DT', function () { 6759 /* Take the brutal approach to cancelling text selection */ 6760 return false; 6761 } ); 6762 } 6763 6764 6765 /** 6766 * Register a callback function. Easily allows a callback function to be added to 6767 * an array store of callback functions that can then all be called together. 6768 * @param {object} oSettings dataTables settings object 6769 * @param {string} sStore Name of the array storage for the callbacks in oSettings 6770 * @param {function} fn Function to be called back 6771 * @param {string} sName Identifying name for the callback (i.e. a label) 6772 * @memberof DataTable#oApi 6773 */ 6774 function _fnCallbackReg( oSettings, sStore, fn, sName ) 6775 { 6776 if ( fn ) 6777 { 6778 oSettings[sStore].push( { 6779 "fn": fn, 6780 "sName": sName 6781 } ); 6782 } 6783 } 6784 6785 6786 /** 6787 * Fire callback functions and trigger events. Note that the loop over the 6788 * callback array store is done backwards! Further note that you do not want to 6789 * fire off triggers in time sensitive applications (for example cell creation) 6790 * as its slow. 6791 * @param {object} settings dataTables settings object 6792 * @param {string} callbackArr Name of the array storage for the callbacks in 6793 * oSettings 6794 * @param {string} eventName Name of the jQuery custom event to trigger. If 6795 * null no trigger is fired 6796 * @param {array} args Array of arguments to pass to the callback function / 6797 * trigger 6798 * @memberof DataTable#oApi 6799 */ 6800 function _fnCallbackFire( settings, callbackArr, eventName, args ) 6801 { 6802 var ret = []; 6803 6804 if ( callbackArr ) { 6805 ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) { 6806 return val.fn.apply( settings.oInstance, args ); 6807 } ); 6808 } 6809 6810 if ( eventName !== null ) { 6811 var e = $.Event( eventName+'.dt' ); 6812 var table = $(settings.nTable); 6813 6814 table.trigger( e, args ); 6815 6816 // If not yet attached to the document, trigger the event 6817 // on the body directly to sort of simulate the bubble 6818 if (table.parents('body').length === 0) { 6819 $('body').trigger( e, args ); 6820 } 6821 6822 ret.push( e.result ); 6823 } 6824 6825 return ret; 6826 } 6827 6828 6829 function _fnLengthOverflow ( settings ) 6830 { 6831 var 6832 start = settings._iDisplayStart, 6833 end = settings.fnDisplayEnd(), 6834 len = settings._iDisplayLength; 6835 6836 /* If we have space to show extra rows (backing up from the end point - then do so */ 6837 if ( start >= end ) 6838 { 6839 start = end - len; 6840 } 6841 6842 // Keep the start record on the current page 6843 start -= (start % len); 6844 6845 if ( len === -1 || start < 0 ) 6846 { 6847 start = 0; 6848 } 6849 6850 settings._iDisplayStart = start; 6851 } 6852 6853 6854 function _fnRenderer( settings, type ) 6855 { 6856 var renderer = settings.renderer; 6857 var host = DataTable.ext.renderer[type]; 6858 6859 if ( $.isPlainObject( renderer ) && renderer[type] ) { 6860 // Specific renderer for this type. If available use it, otherwise use 6861 // the default. 6862 return host[renderer[type]] || host._; 6863 } 6864 else if ( typeof renderer === 'string' ) { 6865 // Common renderer - if there is one available for this type use it, 6866 // otherwise use the default 6867 return host[renderer] || host._; 6868 } 6869 6870 // Use the default 6871 return host._; 6872 } 6873 6874 6875 /** 6876 * Detect the data source being used for the table. Used to simplify the code 6877 * a little (ajax) and to make it compress a little smaller. 6878 * 6879 * @param {object} settings dataTables settings object 6880 * @returns {string} Data source 6881 * @memberof DataTable#oApi 6882 */ 6883 function _fnDataSource ( settings ) 6884 { 6885 if ( settings.oFeatures.bServerSide ) { 6886 return 'ssp'; 6887 } 6888 else if ( settings.ajax || settings.sAjaxSource ) { 6889 return 'ajax'; 6890 } 6891 return 'dom'; 6892 } 6893 6894 6895 6896 6897 /** 6898 * Computed structure of the DataTables API, defined by the options passed to 6899 * `DataTable.Api.register()` when building the API. 6900 * 6901 * The structure is built in order to speed creation and extension of the Api 6902 * objects since the extensions are effectively pre-parsed. 6903 * 6904 * The array is an array of objects with the following structure, where this 6905 * base array represents the Api prototype base: 6906 * 6907 * [ 6908 * { 6909 * name: 'data' -- string - Property name 6910 * val: function () {}, -- function - Api method (or undefined if just an object 6911 * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 6912 * propExt: [ ... ] -- array - Array of Api object definitions to extend the property 6913 * }, 6914 * { 6915 * name: 'row' 6916 * val: {}, 6917 * methodExt: [ ... ], 6918 * propExt: [ 6919 * { 6920 * name: 'data' 6921 * val: function () {}, 6922 * methodExt: [ ... ], 6923 * propExt: [ ... ] 6924 * }, 6925 * ... 6926 * ] 6927 * } 6928 * ] 6929 * 6930 * @type {Array} 6931 * @ignore 6932 */ 6933 var __apiStruct = []; 6934 6935 6936 /** 6937 * `Array.prototype` reference. 6938 * 6939 * @type object 6940 * @ignore 6941 */ 6942 var __arrayProto = Array.prototype; 6943 6944 6945 /** 6946 * Abstraction for `context` parameter of the `Api` constructor to allow it to 6947 * take several different forms for ease of use. 6948 * 6949 * Each of the input parameter types will be converted to a DataTables settings 6950 * object where possible. 6951 * 6952 * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one 6953 * of: 6954 * 6955 * * `string` - jQuery selector. Any DataTables' matching the given selector 6956 * with be found and used. 6957 * * `node` - `TABLE` node which has already been formed into a DataTable. 6958 * * `jQuery` - A jQuery object of `TABLE` nodes. 6959 * * `object` - DataTables settings object 6960 * * `DataTables.Api` - API instance 6961 * @return {array|null} Matching DataTables settings objects. `null` or 6962 * `undefined` is returned if no matching DataTable is found. 6963 * @ignore 6964 */ 6965 var _toSettings = function ( mixed ) 6966 { 6967 var idx, jq; 6968 var settings = DataTable.settings; 6969 var tables = $.map( settings, function (el, i) { 6970 return el.nTable; 6971 } ); 6972 6973 if ( ! mixed ) { 6974 return []; 6975 } 6976 else if ( mixed.nTable && mixed.oApi ) { 6977 // DataTables settings object 6978 return [ mixed ]; 6979 } 6980 else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { 6981 // Table node 6982 idx = $.inArray( mixed, tables ); 6983 return idx !== -1 ? [ settings[idx] ] : null; 6984 } 6985 else if ( mixed && typeof mixed.settings === 'function' ) { 6986 return mixed.settings().toArray(); 6987 } 6988 else if ( typeof mixed === 'string' ) { 6989 // jQuery selector 6990 jq = $(mixed); 6991 } 6992 else if ( mixed instanceof $ ) { 6993 // jQuery object (also DataTables instance) 6994 jq = mixed; 6995 } 6996 6997 if ( jq ) { 6998 return jq.map( function(i) { 6999 idx = $.inArray( this, tables ); 7000 return idx !== -1 ? settings[idx] : null; 7001 } ).toArray(); 7002 } 7003 }; 7004 7005 7006 /** 7007 * DataTables API class - used to control and interface with one or more 7008 * DataTables enhanced tables. 7009 * 7010 * The API class is heavily based on jQuery, presenting a chainable interface 7011 * that you can use to interact with tables. Each instance of the API class has 7012 * a "context" - i.e. the tables that it will operate on. This could be a single 7013 * table, all tables on a page or a sub-set thereof. 7014 * 7015 * Additionally the API is designed to allow you to easily work with the data in 7016 * the tables, retrieving and manipulating it as required. This is done by 7017 * presenting the API class as an array like interface. The contents of the 7018 * array depend upon the actions requested by each method (for example 7019 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
7020 * return an array of objects or arrays depending upon your table's 7021 * configuration). The API object has a number of array like methods (`push`, 7022 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, 7023 * `unique` etc) to assist your working with the data held in a table. 7024 * 7025 * Most methods (those which return an Api instance) are chainable, which means 7026 * the return from a method call also has all of the methods available that the 7027 * top level object had. For example, these two calls are equivalent: 7028 * 7029 * // Not chained 7030 * api.row.add( {...} ); 7031 * api.draw(); 7032 * 7033 * // Chained 7034 * api.row.add( {...} ).draw(); 7035 * 7036 * @class DataTable.Api 7037 * @param {array|object|string|jQuery} context DataTable identifier. This is 7038 * used to define which DataTables enhanced tables this API will operate on. 7039 * Can be one of: 7040 * 7041 * * `string` - jQuery selector. Any DataTables' matching the given selector 7042 * with be found and used. 7043 * * `node` - `TABLE` node which has already been formed into a DataTable. 7044 * * `jQuery` - A jQuery object of `TABLE` nodes. 7045 * * `object` - DataTables settings object 7046 * @param {array} [data] Data to initialise the Api instance with. 7047 * 7048 * @example 7049 * // Direct initialisation during DataTables construction 7050 * var api = $('#example').DataTable(); 7051 * 7052 * @example 7053 * // Initialisation using a DataTables jQuery object 7054 * var api = $('#example').dataTable().api(); 7055 * 7056 * @example 7057 * // Initialisation as a constructor 7058 * var api = new $.fn.DataTable.Api( 'table.dataTable' ); 7059 */ 7060 _Api = function ( context, data ) 7061 { 7062 if ( ! (this instanceof _Api) ) { 7063 return new _Api( context, data ); 7064 } 7065 7066 var settings = []; 7067 var ctxSettings = function ( o ) { 7068 var a = _toSettings( o ); 7069 if ( a ) { 7070 settings.push.apply( settings, a ); 7071 } 7072 }; 7073 7074 if ( Array.isArray( context ) ) { 7075 for ( var i=0, ien=context.length ; i<ien ; i++ ) { 7076 ctxSettings( context[i] ); 7077 } 7078 } 7079 else { 7080 ctxSettings( context ); 7081 } 7082 7083 // Remove duplicates 7084 this.context = _unique( settings ); 7085 7086 // Initial data 7087 if ( data ) { 7088 $.merge( this, data ); 7089 } 7090 7091 // selector 7092 this.selector = { 7093 rows: null, 7094 cols: null, 7095 opts: null 7096 }; 7097 7098 _Api.extend( this, this, __apiStruct ); 7099 }; 7100 7101 DataTable.Api = _Api; 7102 7103 // Don't destroy the existing prototype, just extend it. Required for jQuery 2's 7104 // isPlainObject. 7105 $.extend( _Api.prototype, { 7106 any: function () 7107 { 7108 return this.count() !== 0; 7109 }, 7110 7111 7112 concat: __arrayProto.concat, 7113 7114 7115 context: [], // array of table settings objects 7116 7117 7118 count: function () 7119 { 7120 return this.flatten().length; 7121 }, 7122 7123 7124 each: function ( fn ) 7125 { 7126 for ( var i=0, ien=this.length ; i<ien; i++ ) { 7127 fn.call( this, this[i], i, this ); 7128 } 7129 7130 return this; 7131 }, 7132 7133 7134 eq: function ( idx ) 7135 { 7136 var ctx = this.context; 7137 7138 return ctx.length > idx ? 7139 new _Api( ctx[idx], this[idx] ) : 7140 null; 7141 }, 7142 7143 7144 filter: function ( fn ) 7145 { 7146 var a = []; 7147 7148 if ( __arrayProto.filter ) { 7149 a = __arrayProto.filter.call( this, fn, this ); 7150 } 7151 else { 7152 // Compatibility for browsers without EMCA-252-5 (JS 1.6) 7153 for ( var i=0, ien=this.length ; i<ien ; i++ ) { 7154 if ( fn.call( this, this[i], i, this ) ) { 7155 a.push( this[i] ); 7156 } 7157 } 7158 } 7159 7160 return new _Api( this.context, a ); 7161 }, 7162 7163 7164 flatten: function () 7165 { 7166 var a = []; 7167 return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); 7168 }, 7169 7170 7171 join: __arrayProto.join, 7172 7173 7174 indexOf: __arrayProto.indexOf || function (obj, start) 7175 { 7176 for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) { 7177 if ( this[i] === obj ) { 7178 return i; 7179 } 7180 } 7181 return -1; 7182 }, 7183 7184 iterator: function ( flatten, type, fn, alwaysNew ) { 7185 var 7186 a = [], ret, 7187 i, ien, j, jen, 7188 context = this.context, 7189 rows, items, item, 7190 selector = this.selector; 7191 7192 // Argument shifting 7193 if ( typeof flatten === 'string' ) { 7194 alwaysNew = fn; 7195 fn = type; 7196 type = flatten; 7197 flatten = false; 7198 } 7199 7200 for ( i=0, ien=context.length ; i<ien ; i++ ) { 7201 var apiInst = new _Api( context[i] ); 7202 7203 if ( type === 'table' ) { 7204 ret = fn.call( apiInst, context[i], i ); 7205 7206 if ( ret !== undefined ) { 7207 a.push( ret ); 7208 } 7209 } 7210 else if ( type === 'columns' || type === 'rows' ) { 7211 // this has same length as context - one entry for each table 7212 ret = fn.call( apiInst, context[i], this[i], i ); 7213 7214 if ( ret !== undefined ) { 7215 a.push( ret ); 7216 } 7217 } 7218 else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { 7219 // columns and rows share the same structure. 7220 // 'this' is an array of column indexes for each context 7221 items = this[i]; 7222 7223 if ( type === 'column-rows' ) { 7224 rows = _selector_row_indexes( context[i], selector.opts ); 7225 } 7226 7227 for ( j=0, jen=items.length ; j<jen ; j++ ) { 7228 item = items[j];
vendor: 71,453 bytes, lines 7229-9858
7229 7230 if ( type === 'cell' ) { 7231 ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); 7232 } 7233 else { 7234 ret = fn.call( apiInst, context[i], item, i, j, rows ); 7235 } 7236 7237 if ( ret !== undefined ) { 7238 a.push( ret ); 7239 } 7240 } 7241 } 7242 } 7243 7244 if ( a.length || alwaysNew ) { 7245 var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); 7246 var apiSelector = api.selector; 7247 apiSelector.rows = selector.rows; 7248 apiSelector.cols = selector.cols; 7249 apiSelector.opts = selector.opts; 7250 return api; 7251 } 7252 return this; 7253 }, 7254 7255 7256 lastIndexOf: __arrayProto.lastIndexOf || function (obj, start) 7257 { 7258 // Bit cheeky... 7259 return this.indexOf.apply( this.toArray.reverse(), arguments ); 7260 }, 7261 7262 7263 length: 0, 7264 7265 7266 map: function ( fn ) 7267 { 7268 var a = []; 7269 7270 if ( __arrayProto.map ) { 7271 a = __arrayProto.map.call( this, fn, this ); 7272 } 7273 else { 7274 // Compatibility for browsers without EMCA-252-5 (JS 1.6) 7275 for ( var i=0, ien=this.length ; i<ien ; i++ ) { 7276 a.push( fn.call( this, this[i], i ) ); 7277 } 7278 } 7279 7280 return new _Api( this.context, a ); 7281 }, 7282 7283 7284 pluck: function ( prop ) 7285 { 7286 var fn = DataTable.util.get(prop); 7287 7288 return this.map( function ( el ) { 7289 return fn(el); 7290 } ); 7291 }, 7292 7293 pop: __arrayProto.pop, 7294 7295 7296 push: __arrayProto.push, 7297 7298 7299 // Does not return an API instance 7300 reduce: __arrayProto.reduce || function ( fn, init ) 7301 { 7302 return _fnReduce( this, fn, init, 0, this.length, 1 ); 7303 }, 7304 7305 7306 reduceRight: __arrayProto.reduceRight || function ( fn, init ) 7307 { 7308 return _fnReduce( this, fn, init, this.length-1, -1, -1 ); 7309 }, 7310 7311 7312 reverse: __arrayProto.reverse, 7313 7314 7315 // Object with rows, columns and opts 7316 selector: null, 7317 7318 7319 shift: __arrayProto.shift, 7320 7321 7322 slice: function () { 7323 return new _Api( this.context, this ); 7324 }, 7325 7326 7327 sort: __arrayProto.sort, // ? name - order? 7328 7329 7330 splice: __arrayProto.splice, 7331 7332 7333 toArray: function () 7334 { 7335 return __arrayProto.slice.call( this ); 7336 }, 7337 7338 7339 to$: function () 7340 { 7341 return $( this ); 7342 }, 7343 7344 7345 toJQuery: function () 7346 { 7347 return $( this ); 7348 }, 7349 7350 7351 unique: function () 7352 { 7353 return new _Api( this.context, _unique(this) ); 7354 }, 7355 7356 7357 unshift: __arrayProto.unshift 7358 } ); 7359 7360 7361 _Api.extend = function ( scope, obj, ext ) 7362 { 7363 // Only extend API instances and static properties of the API 7364 if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { 7365 return; 7366 } 7367 7368 var 7369 i, ien, 7370 struct, 7371 methodScoping = function ( scope, fn, struc ) { 7372 return function () { 7373 var ret = fn.apply( scope, arguments ); 7374 7375 // Method extension 7376 _Api.extend( ret, ret, struc.methodExt ); 7377 return ret; 7378 }; 7379 }; 7380 7381 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7382 struct = ext[i]; 7383 7384 // Value 7385 obj[ struct.name ] = struct.type === 'function' ? 7386 methodScoping( scope, struct.val, struct ) : 7387 struct.type === 'object' ? 7388 {} : 7389 struct.val; 7390 7391 obj[ struct.name ].__dt_wrapper = true; 7392 7393 // Property extension 7394 _Api.extend( scope, obj[ struct.name ], struct.propExt ); 7395 } 7396 }; 7397 7398 7399 // @todo - Is there need for an augment function? 7400 // _Api.augment = function ( inst, name ) 7401 // { 7402 // // Find src object in the structure from the name 7403 // var parts = name.split('.'); 7404 7405 // _Api.extend( inst, obj ); 7406 // }; 7407 7408 7409 // [ 7410 // { 7411 // name: 'data' -- string - Property name 7412 // val: function () {}, -- function - Api method (or undefined if just an object 7413 // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result 7414 // propExt: [ ... ] -- array - Array of Api object definitions to extend the property 7415 // }, 7416 // { 7417 // name: 'row' 7418 // val: {}, 7419 // methodExt: [ ... ], 7420 // propExt: [ 7421 // { 7422 // name: 'data' 7423 // val: function () {}, 7424 // methodExt: [ ... ], 7425 // propExt: [ ... ] 7426 // }, 7427 // ... 7428 // ] 7429 // } 7430 // ] 7431 7432 _Api.register = _api_register = function ( name, val ) 7433 { 7434 if ( Array.isArray( name ) ) { 7435 for ( var j=0, jen=name.length ; j<jen ; j++ ) { 7436 _Api.register( name[j], val ); 7437 } 7438 return; 7439 } 7440 7441 var 7442 i, ien, 7443 heir = name.split('.'), 7444 struct = __apiStruct, 7445 key, method; 7446 7447 var find = function ( src, name ) { 7448 for ( var i=0, ien=src.length ; i<ien ; i++ ) { 7449 if ( src[i].name === name ) { 7450 return src[i]; 7451 } 7452 } 7453 return null; 7454 }; 7455 7456 for ( i=0, ien=heir.length ; i<ien ; i++ ) { 7457 method = heir[i].indexOf('()') !== -1; 7458 key = method ? 7459 heir[i].replace('()', '') : 7460 heir[i]; 7461 7462 var src = find( struct, key ); 7463 if ( ! src ) { 7464 src = { 7465 name: key, 7466 val: {}, 7467 methodExt: [], 7468 propExt: [], 7469 type: 'object' 7470 }; 7471 struct.push( src ); 7472 } 7473 7474 if ( i === ien-1 ) { 7475 src.val = val; 7476 src.type = typeof val === 'function' ? 7477 'function' : 7478 $.isPlainObject( val ) ? 7479 'object' : 7480 'other'; 7481 } 7482 else { 7483 struct = method ? 7484 src.methodExt : 7485 src.propExt; 7486 } 7487 } 7488 }; 7489 7490 _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { 7491 _Api.register( pluralName, val ); 7492 7493 _Api.register( singularName, function () { 7494 var ret = val.apply( this, arguments ); 7495 7496 if ( ret === this ) { 7497 // Returned item is the API instance that was passed in, return it 7498 return this; 7499 } 7500 else if ( ret instanceof _Api ) { 7501 // New API instance returned, want the value from the first item 7502 // in the returned array for the singular result. 7503 return ret.length ? 7504 Array.isArray( ret[0] ) ? 7505 new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' 7506 ret[0] : 7507 undefined; 7508 } 7509 7510 // Non-API return - just fire it back 7511 return ret; 7512 } ); 7513 }; 7514 7515 7516 /** 7517 * Selector for HTML tables. Apply the given selector to the give array of 7518 * DataTables settings objects. 7519 * 7520 * @param {string|integer} [selector] jQuery selector string or integer 7521 * @param {array} Array of DataTables settings objects to be filtered 7522 * @return {array} 7523 * @ignore 7524 */ 7525 var __table_selector = function ( selector, a ) 7526 { 7527 if ( Array.isArray(selector) ) { 7528 return $.map( selector, function (item) { 7529 return __table_selector(item, a); 7530 } ); 7531 } 7532 7533 // Integer is used to pick out a table by index 7534 if ( typeof selector === 'number' ) { 7535 return [ a[ selector ] ]; 7536 } 7537 7538 // Perform a jQuery selector on the table nodes 7539 var nodes = $.map( a, function (el, i) { 7540 return el.nTable; 7541 } ); 7542 7543 return $(nodes) 7544 .filter( selector ) 7545 .map( function (i) { 7546 // Need to translate back from the table node to the settings 7547 var idx = $.inArray( this, nodes ); 7548 return a[ idx ]; 7549 } ) 7550 .toArray(); 7551 }; 7552 7553 7554 7555 /** 7556 * Context selector for the API's context (i.e. the tables the API instance 7557 * refers to. 7558 * 7559 * @name DataTable.Api#tables 7560 * @param {string|integer} [selector] Selector to pick which tables the iterator 7561 * should operate on. If not given, all tables in the current context are 7562 * used. This can be given as a jQuery selector (for example `':gt(0)'`) to 7563 * select multiple tables or as an integer to select a single table. 7564 * @returns {DataTable.Api} Returns a new API instance if a selector is given. 7565 */ 7566 _api_register( 'tables()', function ( selector ) { 7567 // A new instance is created if there was a selector specified 7568 return selector !== undefined && selector !== null ? 7569 new _Api( __table_selector( selector, this.context ) ) : 7570 this; 7571 } ); 7572 7573 7574 _api_register( 'table()', function ( selector ) { 7575 var tables = this.tables( selector ); 7576 var ctx = tables.context; 7577 7578 // Truncate to the first matched table 7579 return ctx.length ? 7580 new _Api( ctx[0] ) : 7581 tables; 7582 } ); 7583 7584 7585 _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { 7586 return this.iterator( 'table', function ( ctx ) { 7587 return ctx.nTable; 7588 }, 1 ); 7589 } ); 7590 7591 7592 _api_registerPlural( 'tables().body()', 'table().body()' , function () { 7593 return this.iterator( 'table', function ( ctx ) { 7594 return ctx.nTBody; 7595 }, 1 ); 7596 } ); 7597 7598 7599 _api_registerPlural( 'tables().header()', 'table().header()' , function () { 7600 return this.iterator( 'table', function ( ctx ) { 7601 return ctx.nTHead; 7602 }, 1 ); 7603 } ); 7604 7605 7606 _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { 7607 return this.iterator( 'table', function ( ctx ) { 7608 return ctx.nTFoot; 7609 }, 1 ); 7610 } ); 7611 7612 7613 _api_registerPlural( 'tables().containers()', 'table().container()' , function () { 7614 return this.iterator( 'table', function ( ctx ) { 7615 return ctx.nTableWrapper; 7616 }, 1 ); 7617 } ); 7618 7619 7620 7621 /** 7622 * Redraw the tables in the current context. 7623 */ 7624 _api_register( 'draw()', function ( paging ) { 7625 return this.iterator( 'table', function ( settings ) { 7626 if ( paging === 'page' ) { 7627 _fnDraw( settings ); 7628 } 7629 else { 7630 if ( typeof paging === 'string' ) { 7631 paging = paging === 'full-hold' ? 7632 false : 7633 true; 7634 } 7635 7636 _fnReDraw( settings, paging===false ); 7637 } 7638 } ); 7639 } ); 7640 7641 7642 7643 /** 7644 * Get the current page index. 7645 * 7646 * @return {integer} Current page index (zero based) 7647 *//** 7648 * Set the current page. 7649 * 7650 * Note that if you attempt to show a page which does not exist, DataTables will 7651 * not throw an error, but rather reset the paging. 7652 * 7653 * @param {integer|string} action The paging action to take. This can be one of: 7654 * * `integer` - The page index to jump to 7655 * * `string` - An action to take: 7656 * * `first` - Jump to first page. 7657 * * `next` - Jump to the next page 7658 * * `previous` - Jump to previous page 7659 * * `last` - Jump to the last page. 7660 * @returns {DataTables.Api} this 7661 */ 7662 _api_register( 'page()', function ( action ) { 7663 if ( action === undefined ) { 7664 return this.page.info().page; // not an expensive call 7665 } 7666 7667 // else, have an action to take on all tables 7668 return this.iterator( 'table', function ( settings ) { 7669 _fnPageChange( settings, action ); 7670 } ); 7671 } ); 7672 7673 7674 /** 7675 * Paging information for the first table in the current context. 7676 * 7677 * If you require paging information for another table, use the `table()` method 7678 * with a suitable selector. 7679 * 7680 * @return {object} Object with the following properties set: 7681 * * `page` - Current page index (zero based - i.e. the first page is `0`) 7682 * * `pages` - Total number of pages 7683 * * `start` - Display index for the first record shown on the current page 7684 * * `end` - Display index for the last record shown on the current page 7685 * * `length` - Display length (number of records). Note that generally `start 7686 * + length = end`, but this is not always true, for example if there are 7687 * only 2 records to show on the final page, with a length of 10. 7688 * * `recordsTotal` - Full data set length 7689 * * `recordsDisplay` - Data set length once the current filtering criterion 7690 * are applied. 7691 */ 7692 _api_register( 'page.info()', function ( action ) { 7693 if ( this.context.length === 0 ) { 7694 return undefined; 7695 } 7696 7697 var 7698 settings = this.context[0], 7699 start = settings._iDisplayStart, 7700 len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, 7701 visRecords = settings.fnRecordsDisplay(), 7702 all = len === -1; 7703 7704 return { 7705 "page": all ? 0 : Math.floor( start / len ), 7706 "pages": all ? 1 : Math.ceil( visRecords / len ), 7707 "start": start, 7708 "end": settings.fnDisplayEnd(), 7709 "length": len, 7710 "recordsTotal": settings.fnRecordsTotal(), 7711 "recordsDisplay": visRecords, 7712 "serverSide": _fnDataSource( settings ) === 'ssp' 7713 }; 7714 } ); 7715 7716 7717 /** 7718 * Get the current page length. 7719 * 7720 * @return {integer} Current page length. Note `-1` indicates that all records 7721 * are to be shown. 7722 *//** 7723 * Set the current page length. 7724 * 7725 * @param {integer} Page length to set. Use `-1` to show all records. 7726 * @returns {DataTables.Api} this 7727 */ 7728 _api_register( 'page.len()', function ( len ) { 7729 // Note that we can't call this function 'length()' because `length` 7730 // is a Javascript property of functions which defines how many arguments 7731 // the function expects. 7732 if ( len === undefined ) { 7733 return this.context.length !== 0 ? 7734 this.context[0]._iDisplayLength : 7735 undefined; 7736 } 7737 7738 // else, set the page length 7739 return this.iterator( 'table', function ( settings ) { 7740 _fnLengthChange( settings, len ); 7741 } ); 7742 } ); 7743 7744 7745 7746 var __reload = function ( settings, holdPosition, callback ) { 7747 // Use the draw event to trigger a callback 7748 if ( callback ) { 7749 var api = new _Api( settings ); 7750 7751 api.one( 'draw', function () { 7752 callback( api.ajax.json() ); 7753 } ); 7754 } 7755 7756 if ( _fnDataSource( settings ) == 'ssp' ) { 7757 _fnReDraw( settings, holdPosition ); 7758 } 7759 else { 7760 _fnProcessingDisplay( settings, true ); 7761 7762 // Cancel an existing request 7763 var xhr = settings.jqXHR; 7764 if ( xhr && xhr.readyState !== 4 ) { 7765 xhr.abort(); 7766 } 7767 7768 // Trigger xhr 7769 _fnBuildAjax( settings, [], function( json ) { 7770 _fnClearTable( settings ); 7771 7772 var data = _fnAjaxDataSrc( settings, json ); 7773 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 7774 _fnAddData( settings, data[i] ); 7775 } 7776 7777 _fnReDraw( settings, holdPosition ); 7778 _fnProcessingDisplay( settings, false ); 7779 } ); 7780 } 7781 }; 7782 7783 7784 /** 7785 * Get the JSON response from the last Ajax request that DataTables made to the 7786 * server. Note that this returns the JSON from the first table in the current 7787 * context. 7788 * 7789 * @return {object} JSON received from the server. 7790 */ 7791 _api_register( 'ajax.json()', function () { 7792 var ctx = this.context; 7793 7794 if ( ctx.length > 0 ) { 7795 return ctx[0].json; 7796 } 7797 7798 // else return undefined; 7799 } ); 7800 7801 7802 /** 7803 * Get the data submitted in the last Ajax request 7804 */ 7805 _api_register( 'ajax.params()', function () { 7806 var ctx = this.context; 7807 7808 if ( ctx.length > 0 ) { 7809 return ctx[0].oAjaxData; 7810 } 7811 7812 // else return undefined; 7813 } ); 7814 7815 7816 /** 7817 * Reload tables from the Ajax data source. Note that this function will 7818 * automatically re-draw the table when the remote data has been loaded. 7819 * 7820 * @param {boolean} [reset=true] Reset (default) or hold the current paging 7821 * position. A full re-sort and re-filter is performed when this method is 7822 * called, which is why the pagination reset is the default action. 7823 * @returns {DataTables.Api} this 7824 */ 7825 _api_register( 'ajax.reload()', function ( callback, resetPaging ) { 7826 return this.iterator( 'table', function (settings) { 7827 __reload( settings, resetPaging===false, callback ); 7828 } ); 7829 } ); 7830 7831 7832 /** 7833 * Get the current Ajax URL. Note that this returns the URL from the first 7834 * table in the current context. 7835 * 7836 * @return {string} Current Ajax source URL 7837 *//** 7838 * Set the Ajax URL. Note that this will set the URL for all tables in the 7839 * current context. 7840 * 7841 * @param {string} url URL to set. 7842 * @returns {DataTables.Api} this 7843 */ 7844 _api_register( 'ajax.url()', function ( url ) { 7845 var ctx = this.context; 7846 7847 if ( url === undefined ) { 7848 // get 7849 if ( ctx.length === 0 ) { 7850 return undefined; 7851 } 7852 ctx = ctx[0]; 7853 7854 return ctx.ajax ? 7855 $.isPlainObject( ctx.ajax ) ? 7856 ctx.ajax.url : 7857 ctx.ajax : 7858 ctx.sAjaxSource; 7859 } 7860 7861 // set 7862 return this.iterator( 'table', function ( settings ) { 7863 if ( $.isPlainObject( settings.ajax ) ) { 7864 settings.ajax.url = url; 7865 } 7866 else { 7867 settings.ajax = url; 7868 } 7869 // No need to consider sAjaxSource here since DataTables gives priority 7870 // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any 7871 // value of `sAjaxSource` redundant. 7872 } ); 7873 } ); 7874 7875 7876 /** 7877 * Load data from the newly set Ajax URL. Note that this method is only 7878 * available when `ajax.url()` is used to set a URL. Additionally, this method 7879 * has the same effect as calling `ajax.reload()` but is provided for 7880 * convenience when setting a new URL. Like `ajax.reload()` it will 7881 * automatically redraw the table once the remote data has been loaded. 7882 * 7883 * @returns {DataTables.Api} this 7884 */ 7885 _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { 7886 // Same as a reload, but makes sense to present it for easy access after a 7887 // url change 7888 return this.iterator( 'table', function ( ctx ) { 7889 __reload( ctx, resetPaging===false, callback ); 7890 } ); 7891 } ); 7892 7893 7894 7895 7896 var _selector_run = function ( type, selector, selectFn, settings, opts ) 7897 { 7898 var 7899 out = [], res, 7900 a, i, ien, j, jen, 7901 selectorType = typeof selector; 7902 7903 // Can't just check for isArray here, as an API or jQuery instance might be 7904 // given with their array like look 7905 if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { 7906 selector = [ selector ]; 7907 } 7908 7909 for ( i=0, ien=selector.length ; i<ien ; i++ ) { 7910 // Only split on simple strings - complex expressions will be jQuery selectors 7911 a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ? 7912 selector[i].split(',') : 7913 [ selector[i] ]; 7914 7915 for ( j=0, jen=a.length ; j<jen ; j++ ) { 7916 res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); 7917 7918 if ( res && res.length ) { 7919 out = out.concat( res ); 7920 } 7921 } 7922 } 7923 7924 // selector extensions 7925 var ext = _ext.selector[ type ]; 7926 if ( ext.length ) { 7927 for ( i=0, ien=ext.length ; i<ien ; i++ ) { 7928 out = ext[i]( settings, opts, out ); 7929 } 7930 } 7931 7932 return _unique( out ); 7933 }; 7934 7935 7936 var _selector_opts = function ( opts ) 7937 { 7938 if ( ! opts ) { 7939 opts = {}; 7940 } 7941 7942 // Backwards compatibility for 1.9- which used the terminology filter rather 7943 // than search 7944 if ( opts.filter && opts.search === undefined ) { 7945 opts.search = opts.filter; 7946 } 7947 7948 return $.extend( { 7949 search: 'none', 7950 order: 'current', 7951 page: 'all' 7952 }, opts ); 7953 }; 7954 7955 7956 var _selector_first = function ( inst ) 7957 { 7958 // Reduce the API instance to the first item found 7959 for ( var i=0, ien=inst.length ; i<ien ; i++ ) { 7960 if ( inst[i].length > 0 ) { 7961 // Assign the first element to the first item in the instance 7962 // and truncate the instance and context 7963 inst[0] = inst[i]; 7964 inst[0].length = 1; 7965 inst.length = 1; 7966 inst.context = [ inst.context[i] ]; 7967 7968 return inst; 7969 } 7970 } 7971 7972 // Not found - return an empty instance 7973 inst.length = 0; 7974 return inst; 7975 }; 7976 7977 7978 var _selector_row_indexes = function ( settings, opts ) 7979 { 7980 var 7981 i, ien, tmp, a=[], 7982 displayFiltered = settings.aiDisplay, 7983 displayMaster = settings.aiDisplayMaster; 7984 7985 var 7986 search = opts.search, // none, applied, removed 7987 order = opts.order, // applied, current, index (original - compatibility with 1.9) 7988 page = opts.page; // all, current 7989 7990 if ( _fnDataSource( settings ) == 'ssp' ) { 7991 // In server-side processing mode, most options are irrelevant since 7992 // rows not shown don't exist and the index order is the applied order 7993 // Removed is a special case - for consistency just return an empty 7994 // array 7995 return search === 'removed' ? 7996 [] : 7997 _range( 0, displayMaster.length ); 7998 } 7999 else if ( page == 'current' ) { 8000 // Current page implies that order=current and filter=applied, since it is 8001 // fairly senseless otherwise, regardless of what order and search actually 8002 // are 8003 for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { 8004 a.push( displayFiltered[i] ); 8005 } 8006 } 8007 else if ( order == 'current' || order == 'applied' ) { 8008 if ( search == 'none') { 8009 a = displayMaster.slice(); 8010 } 8011 else if ( search == 'applied' ) { 8012 a = displayFiltered.slice(); 8013 } 8014 else if ( search == 'removed' ) { 8015 // O(n+m) solution by creating a hash map 8016 var displayFilteredMap = {}; 8017 8018 for ( var i=0, ien=displayFiltered.length ; i<ien ; i++ ) { 8019 displayFilteredMap[displayFiltered[i]] = null; 8020 } 8021 8022 a = $.map( displayMaster, function (el) { 8023 return ! displayFilteredMap.hasOwnProperty(el) ? 8024 el : 8025 null; 8026 } ); 8027 } 8028 } 8029 else if ( order == 'index' || order == 'original' ) { 8030 for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 8031 if ( search == 'none' ) { 8032 a.push( i ); 8033 } 8034 else { // applied | removed 8035 tmp = $.inArray( i, displayFiltered ); 8036 8037 if ((tmp === -1 && search == 'removed') || 8038 (tmp >= 0 && search == 'applied') ) 8039 { 8040 a.push( i ); 8041 } 8042 } 8043 } 8044 } 8045 8046 return a; 8047 }; 8048 8049 8050 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 8051 * Rows 8052 * 8053 * {} - no selector - use all available rows 8054 * {integer} - row aoData index 8055 * {node} - TR node 8056 * {string} - jQuery selector to apply to the TR elements 8057 * {array} - jQuery array of nodes, or simply an array of TR nodes 8058 * 8059 */ 8060 var __row_selector = function ( settings, selector, opts ) 8061 { 8062 var rows; 8063 var run = function ( sel ) { 8064 var selInt = _intVal( sel ); 8065 var i, ien; 8066 var aoData = settings.aoData; 8067 8068 // Short cut - selector is a number and no options provided (default is 8069 // all records, so no need to check if the index is in there, since it 8070 // must be - dev error if the index doesn't exist). 8071 if ( selInt !== null && ! opts ) { 8072 return [ selInt ]; 8073 } 8074 8075 if ( ! rows ) { 8076 rows = _selector_row_indexes( settings, opts ); 8077 } 8078 8079 if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) { 8080 // Selector - integer 8081 return [ selInt ]; 8082 } 8083 else if ( sel === null || sel === undefined || sel === '' ) { 8084 // Selector - none 8085 return rows; 8086 } 8087 8088 // Selector - function 8089 if ( typeof sel === 'function' ) { 8090 return $.map( rows, function (idx) { 8091 var row = aoData[ idx ]; 8092 return sel( idx, row._aData, row.nTr ) ? idx : null; 8093 } ); 8094 } 8095 8096 // Selector - node 8097 if ( sel.nodeName ) { 8098 var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup 8099 var cellIdx = sel._DT_CellIndex; 8100 8101 if ( rowIdx !== undefined ) { 8102 // Make sure that the row is actually still present in the table 8103 return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? 8104 [ rowIdx ] : 8105 []; 8106 } 8107 else if ( cellIdx ) { 8108 return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? 8109 [ cellIdx.row ] : 8110 []; 8111 } 8112 else { 8113 var host = $(sel).closest('*[data-dt-row]'); 8114 return host.length ? 8115 [ host.data('dt-row') ] : 8116 []; 8117 } 8118 } 8119 8120 // ID selector. Want to always be able to select rows by id, regardless 8121 // of if the tr element has been created or not, so can't rely upon 8122 // jQuery here - hence a custom implementation. This does not match 8123 // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, 8124 // but to select it using a CSS selector engine (like Sizzle or 8125 // querySelect) it would need to need to be escaped for some characters. 8126 // DataTables simplifies this for row selectors since you can select 8127 // only a row. A # indicates an id any anything that follows is the id - 8128 // unescaped. 8129 if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { 8130 // get row index from id 8131 var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; 8132 if ( rowObj !== undefined ) { 8133 return [ rowObj.idx ]; 8134 } 8135 8136 // need to fall through to jQuery in case there is DOM id that 8137 // matches 8138 } 8139 8140 // Get nodes in the order from the `rows` array with null values removed 8141 var nodes = _removeEmpty( 8142 _pluck_order( settings.aoData, rows, 'nTr' ) 8143 ); 8144 8145 // Selector - jQuery selector string, array of nodes or jQuery object/ 8146 // As jQuery's .filter() allows jQuery objects to be passed in filter, 8147 // it also allows arrays, so this will cope with all three options 8148 return $(nodes) 8149 .filter( sel ) 8150 .map( function () { 8151 return this._DT_RowIndex; 8152 } ) 8153 .toArray(); 8154 }; 8155 8156 return _selector_run( 'row', selector, run, settings, opts ); 8157 }; 8158 8159 8160 _api_register( 'rows()', function ( selector, opts ) { 8161 // argument shifting 8162 if ( selector === undefined ) { 8163 selector = ''; 8164 } 8165 else if ( $.isPlainObject( selector ) ) { 8166 opts = selector; 8167 selector = ''; 8168 } 8169 8170 opts = _selector_opts( opts ); 8171 8172 var inst = this.iterator( 'table', function ( settings ) { 8173 return __row_selector( settings, selector, opts ); 8174 }, 1 ); 8175 8176 // Want argument shifting here and in __row_selector? 8177 inst.selector.rows = selector; 8178 inst.selector.opts = opts; 8179 8180 return inst; 8181 } ); 8182 8183 _api_register( 'rows().nodes()', function () { 8184 return this.iterator( 'row', function ( settings, row ) { 8185 return settings.aoData[ row ].nTr || undefined; 8186 }, 1 ); 8187 } ); 8188 8189 _api_register( 'rows().data()', function () { 8190 return this.iterator( true, 'rows', function ( settings, rows ) { 8191 return _pluck_order( settings.aoData, rows, '_aData' ); 8192 }, 1 ); 8193 } ); 8194 8195 _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { 8196 return this.iterator( 'row', function ( settings, row ) { 8197 var r = settings.aoData[ row ]; 8198 return type === 'search' ? r._aFilterData : r._aSortData; 8199 }, 1 ); 8200 } ); 8201 8202 _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { 8203 return this.iterator( 'row', function ( settings, row ) { 8204 _fnInvalidate( settings, row, src ); 8205 } ); 8206 } ); 8207 8208 _api_registerPlural( 'rows().indexes()', 'row().index()', function () { 8209 return this.iterator( 'row', function ( settings, row ) { 8210 return row; 8211 }, 1 ); 8212 } ); 8213 8214 _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { 8215 var a = []; 8216 var context = this.context; 8217 8218 // `iterator` will drop undefined values, but in this case we want them 8219 for ( var i=0, ien=context.length ; i<ien ; i++ ) { 8220 for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { 8221 var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); 8222 a.push( (hash === true ? '#' : '' )+ id ); 8223 } 8224 } 8225 8226 return new _Api( context, a ); 8227 } ); 8228 8229 _api_registerPlural( 'rows().remove()', 'row().remove()', function () { 8230 var that = this; 8231 8232 this.iterator( 'row', function ( settings, row, thatIdx ) { 8233 var data = settings.aoData; 8234 var rowData = data[ row ]; 8235 var i, ien, j, jen; 8236 var loopRow, loopCells; 8237 8238 data.splice( row, 1 ); 8239 8240 // Update the cached indexes 8241 for ( i=0, ien=data.length ; i<ien ; i++ ) { 8242 loopRow = data[i]; 8243 loopCells = loopRow.anCells; 8244 8245 // Rows 8246 if ( loopRow.nTr !== null ) { 8247 loopRow.nTr._DT_RowIndex = i; 8248 } 8249 8250 // Cells 8251 if ( loopCells !== null ) { 8252 for ( j=0, jen=loopCells.length ; j<jen ; j++ ) { 8253 loopCells[j]._DT_CellIndex.row = i; 8254 } 8255 } 8256 } 8257 8258 // Delete from the display arrays 8259 _fnDeleteIndex( settings.aiDisplayMaster, row ); 8260 _fnDeleteIndex( settings.aiDisplay, row ); 8261 _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes 8262 8263 // For server-side processing tables - subtract the deleted row from the count 8264 if ( settings._iRecordsDisplay > 0 ) { 8265 settings._iRecordsDisplay--; 8266 } 8267 8268 // Check for an 'overflow' they case for displaying the table 8269 _fnLengthOverflow( settings ); 8270 8271 // Remove the row's ID reference if there is one 8272 var id = settings.rowIdFn( rowData._aData ); 8273 if ( id !== undefined ) { 8274 delete settings.aIds[ id ]; 8275 } 8276 } ); 8277 8278 this.iterator( 'table', function ( settings ) { 8279 for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { 8280 settings.aoData[i].idx = i; 8281 } 8282 } ); 8283 8284 return this; 8285 } ); 8286 8287 8288 _api_register( 'rows.add()', function ( rows ) { 8289 var newRows = this.iterator( 'table', function ( settings ) { 8290 var row, i, ien; 8291 var out = []; 8292 8293 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 8294 row = rows[i]; 8295 8296 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8297 out.push( _fnAddTr( settings, row )[0] ); 8298 } 8299 else { 8300 out.push( _fnAddData( settings, row ) ); 8301 } 8302 } 8303 8304 return out; 8305 }, 1 ); 8306 8307 // Return an Api.rows() extended instance, so rows().nodes() etc can be used 8308 var modRows = this.rows( -1 ); 8309 modRows.pop(); 8310 $.merge( modRows, newRows ); 8311 8312 return modRows; 8313 } ); 8314 8315 8316 8317 8318 8319 /** 8320 * 8321 */ 8322 _api_register( 'row()', function ( selector, opts ) { 8323 return _selector_first( this.rows( selector, opts ) ); 8324 } ); 8325 8326 8327 _api_register( 'row().data()', function ( data ) { 8328 var ctx = this.context; 8329 8330 if ( data === undefined ) { 8331 // Get 8332 return ctx.length && this.length ? 8333 ctx[0].aoData[ this[0] ]._aData : 8334 undefined; 8335 } 8336 8337 // Set 8338 var row = ctx[0].aoData[ this[0] ]; 8339 row._aData = data; 8340 8341 // If the DOM has an id, and the data source is an array 8342 if ( Array.isArray( data ) && row.nTr && row.nTr.id ) { 8343 _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); 8344 } 8345 8346 // Automatically invalidate 8347 _fnInvalidate( ctx[0], this[0], 'data' ); 8348 8349 return this; 8350 } ); 8351 8352 8353 _api_register( 'row().node()', function () { 8354 var ctx = this.context; 8355 8356 return ctx.length && this.length ? 8357 ctx[0].aoData[ this[0] ].nTr || null : 8358 null; 8359 } ); 8360 8361 8362 _api_register( 'row.add()', function ( row ) { 8363 // Allow a jQuery object to be passed in - only a single row is added from 8364 // it though - the first element in the set 8365 if ( row instanceof $ && row.length ) { 8366 row = row[0]; 8367 } 8368 8369 var rows = this.iterator( 'table', function ( settings ) { 8370 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { 8371 return _fnAddTr( settings, row )[0]; 8372 } 8373 return _fnAddData( settings, row ); 8374 } ); 8375 8376 // Return an Api.rows() extended instance, with the newly added row selected 8377 return this.row( rows[0] ); 8378 } ); 8379 8380 8381 $(document).on('plugin-init.dt', function (e, context) { 8382 var api = new _Api( context ); 8383 var namespace = 'on-plugin-init'; 8384 var stateSaveParamsEvent = 'stateSaveParams.' + namespace; 8385 var destroyEvent = 'destroy. ' + namespace; 8386 8387 api.on( stateSaveParamsEvent, function ( e, settings, d ) { 8388 // This could be more compact with the API, but it is a lot faster as a simple 8389 // internal loop 8390 var idFn = settings.rowIdFn; 8391 var data = settings.aoData; 8392 var ids = []; 8393 8394 for (var i=0 ; i<data.length ; i++) { 8395 if (data[i]._detailsShow) { 8396 ids.push( '#' + idFn(data[i]._aData) ); 8397 } 8398 } 8399 8400 d.childRows = ids; 8401 }); 8402 8403 api.on( destroyEvent, function () { 8404 api.off(stateSaveParamsEvent + ' ' + destroyEvent); 8405 }); 8406 8407 var loaded = api.state.loaded(); 8408 8409 if ( loaded && loaded.childRows ) { 8410 api 8411 .rows( $.map(loaded.childRows, function (id){ 8412 return id.replace(/:/g, '\\:') 8413 }) ) 8414 .every( function () { 8415 _fnCallbackFire( context, null, 'requestChild', [ this ] ) 8416 }); 8417 } 8418 }); 8419 8420 var __details_add = function ( ctx, row, data, klass ) 8421 { 8422 // Convert to array of TR elements 8423 var rows = []; 8424 var addRow = function ( r, k ) { 8425 // Recursion to allow for arrays of jQuery objects 8426 if ( Array.isArray( r ) || r instanceof $ ) { 8427 for ( var i=0, ien=r.length ; i<ien ; i++ ) { 8428 addRow( r[i], k ); 8429 } 8430 return; 8431 } 8432 8433 // If we get a TR element, then just add it directly - up to the dev 8434 // to add the correct number of columns etc 8435 if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { 8436 rows.push( r ); 8437 } 8438 else { 8439 // Otherwise create a row with a wrapper 8440 var created = $('<tr><td></td></tr>').addClass( k ); 8441 $('td', created) 8442 .addClass( k ) 8443 .html( r ) 8444 [0].colSpan = _fnVisbleColumns( ctx ); 8445 8446 rows.push( created[0] ); 8447 } 8448 }; 8449 8450 addRow( data, klass ); 8451 8452 if ( row._details ) { 8453 row._details.detach(); 8454 } 8455 8456 row._details = $(rows); 8457 8458 // If the children were already shown, that state should be retained 8459 if ( row._detailsShow ) { 8460 row._details.insertAfter( row.nTr ); 8461 } 8462 }; 8463 8464 8465 // Make state saving of child row details async to allow them to be batch processed 8466 var __details_state = DataTable.util.throttle( 8467 function (ctx) { 8468 _fnSaveState( ctx[0] ) 8469 }, 8470 500 8471 ); 8472 8473 8474 var __details_remove = function ( api, idx ) 8475 { 8476 var ctx = api.context; 8477 8478 if ( ctx.length ) { 8479 var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; 8480 8481 if ( row && row._details ) { 8482 row._details.remove(); 8483 8484 row._detailsShow = undefined; 8485 row._details = undefined; 8486 $( row.nTr ).removeClass( 'dt-hasChild' ); 8487 __details_state( ctx ); 8488 } 8489 } 8490 }; 8491 8492 8493 var __details_display = function ( api, show ) { 8494 var ctx = api.context; 8495 8496 if ( ctx.length && api.length ) { 8497 var row = ctx[0].aoData[ api[0] ]; 8498 8499 if ( row._details ) { 8500 row._detailsShow = show; 8501 8502 if ( show ) { 8503 row._details.insertAfter( row.nTr ); 8504 $( row.nTr ).addClass( 'dt-hasChild' ); 8505 } 8506 else { 8507 row._details.detach(); 8508 $( row.nTr ).removeClass( 'dt-hasChild' ); 8509 } 8510 8511 _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] ) 8512 8513 __details_events( ctx[0] ); 8514 __details_state( ctx ); 8515 } 8516 } 8517 }; 8518 8519 8520 var __details_events = function ( settings ) 8521 { 8522 var api = new _Api( settings ); 8523 var namespace = '.dt.DT_details'; 8524 var drawEvent = 'draw'+namespace; 8525 var colvisEvent = 'column-sizing'+namespace; 8526 var destroyEvent = 'destroy'+namespace; 8527 var data = settings.aoData; 8528 8529 api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); 8530 8531 if ( _pluck( data, '_details' ).length > 0 ) { 8532 // On each draw, insert the required elements into the document 8533 api.on( drawEvent, function ( e, ctx ) { 8534 if ( settings !== ctx ) { 8535 return; 8536 } 8537 8538 api.rows( {page:'current'} ).eq(0).each( function (idx) { 8539 // Internal data grab 8540 var row = data[ idx ]; 8541 8542 if ( row._detailsShow ) { 8543 row._details.insertAfter( row.nTr ); 8544 } 8545 } ); 8546 } ); 8547 8548 // Column visibility change - update the colspan 8549 api.on( colvisEvent, function ( e, ctx, idx, vis ) { 8550 if ( settings !== ctx ) { 8551 return; 8552 } 8553 8554 // Update the colspan for the details rows (note, only if it already has 8555 // a colspan) 8556 var row, visible = _fnVisbleColumns( ctx ); 8557 8558 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8559 row = data[i]; 8560 8561 if ( row._details ) { 8562 row._details.children('td[colspan]').attr('colspan', visible ); 8563 } 8564 } 8565 } ); 8566 8567 // Table destroyed - nuke any child rows 8568 api.on( destroyEvent, function ( e, ctx ) { 8569 if ( settings !== ctx ) { 8570 return; 8571 } 8572 8573 for ( var i=0, ien=data.length ; i<ien ; i++ ) { 8574 if ( data[i]._details ) { 8575 __details_remove( api, i ); 8576 } 8577 } 8578 } ); 8579 } 8580 }; 8581 8582 // Strings for the method names to help minification 8583 var _emp = ''; 8584 var _child_obj = _emp+'row().child'; 8585 var _child_mth = _child_obj+'()'; 8586 8587 // data can be: 8588 // tr 8589 // string 8590 // jQuery or array of any of the above 8591 _api_register( _child_mth, function ( data, klass ) { 8592 var ctx = this.context; 8593 8594 if ( data === undefined ) { 8595 // get 8596 return ctx.length && this.length ? 8597 ctx[0].aoData[ this[0] ]._details : 8598 undefined; 8599 } 8600 else if ( data === true ) { 8601 // show 8602 this.child.show(); 8603 } 8604 else if ( data === false ) { 8605 // remove 8606 __details_remove( this ); 8607 } 8608 else if ( ctx.length && this.length ) { 8609 // set 8610 __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); 8611 } 8612 8613 return this; 8614 } ); 8615 8616 8617 _api_register( [ 8618 _child_obj+'.show()', 8619 _child_mth+'.show()' // only when `child()` was called with parameters (without 8620 ], function ( show ) { // it returns an object and this method is not executed) 8621 __details_display( this, true ); 8622 return this; 8623 } ); 8624 8625 8626 _api_register( [ 8627 _child_obj+'.hide()', 8628 _child_mth+'.hide()' // only when `child()` was called with parameters (without 8629 ], function () { // it returns an object and this method is not executed) 8630 __details_display( this, false ); 8631 return this; 8632 } ); 8633 8634 8635 _api_register( [ 8636 _child_obj+'.remove()', 8637 _child_mth+'.remove()' // only when `child()` was called with parameters (without 8638 ], function () { // it returns an object and this method is not executed) 8639 __details_remove( this ); 8640 return this; 8641 } ); 8642 8643 8644 _api_register( _child_obj+'.isShown()', function () { 8645 var ctx = this.context; 8646 8647 if ( ctx.length && this.length ) { 8648 // _detailsShown as false or undefined will fall through to return false 8649 return ctx[0].aoData[ this[0] ]._detailsShow || false; 8650 } 8651 return false; 8652 } ); 8653 8654 8655 8656 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 8657 * Columns 8658 * 8659 * {integer} - column index (>=0 count from left, <0 count from right) 8660 * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) 8661 * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) 8662 * "{string}:name" - column name 8663 * "{string}" - jQuery selector on column header nodes 8664 * 8665 */ 8666 8667 // can be an array of these items, comma separated list, or an array of comma 8668 // separated lists 8669 8670 var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/; 8671 8672 8673 // r1 and r2 are redundant - but it means that the parameters match for the 8674 // iterator callback in columns().data() 8675 var __columnData = function ( settings, column, r1, r2, rows ) { 8676 var a = []; 8677 for ( var row=0, ien=rows.length ; row<ien ; row++ ) { 8678 a.push( _fnGetCellData( settings, rows[row], column ) ); 8679 } 8680 return a; 8681 }; 8682 8683 8684 var __column_selector = function ( settings, selector, opts ) 8685 { 8686 var 8687 columns = settings.aoColumns, 8688 names = _pluck( columns, 'sName' ), 8689 nodes = _pluck( columns, 'nTh' ); 8690 8691 var run = function ( s ) { 8692 var selInt = _intVal( s ); 8693 8694 // Selector - all 8695 if ( s === '' ) { 8696 return _range( columns.length ); 8697 } 8698 8699 // Selector - index 8700 if ( selInt !== null ) { 8701 return [ selInt >= 0 ? 8702 selInt : // Count from left 8703 columns.length + selInt // Count from right (+ because its a negative value) 8704 ]; 8705 } 8706 8707 // Selector = function 8708 if ( typeof s === 'function' ) { 8709 var rows = _selector_row_indexes( settings, opts ); 8710 8711 return $.map( columns, function (col, idx) { 8712 return s( 8713 idx, 8714 __columnData( settings, idx, 0, 0, rows ), 8715 nodes[ idx ] 8716 ) ? idx : null; 8717 } ); 8718 } 8719 8720 // jQuery or string selector 8721 var match = typeof s === 'string' ? 8722 s.match( __re_column_selector ) : 8723 ''; 8724 8725 if ( match ) { 8726 switch( match[2] ) { 8727 case 'visIdx': 8728 case 'visible': 8729 var idx = parseInt( match[1], 10 ); 8730 // Visible index given, convert to column index 8731 if ( idx < 0 ) { 8732 // Counting from the right 8733 var visColumns = $.map( columns, function (col,i) { 8734 return col.bVisible ? i : null; 8735 } ); 8736 return [ visColumns[ visColumns.length + idx ] ]; 8737 } 8738 // Counting from the left 8739 return [ _fnVisibleToColumnIndex( settings, idx ) ]; 8740 8741 case 'name': 8742 // match by name. `names` is column index complete and in order 8743 return $.map( names, function (name, i) { 8744 return name === match[1] ? i : null; 8745 } ); 8746 8747 default: 8748 return []; 8749 } 8750 } 8751 8752 // Cell in the table body 8753 if ( s.nodeName && s._DT_CellIndex ) { 8754 return [ s._DT_CellIndex.column ]; 8755 } 8756 8757 // jQuery selector on the TH elements for the columns 8758 var jqResult = $( nodes ) 8759 .filter( s ) 8760 .map( function () { 8761 return $.inArray( this, nodes ); // `nodes` is column index complete and in order 8762 } ) 8763 .toArray(); 8764 8765 if ( jqResult.length || ! s.nodeName ) { 8766 return jqResult; 8767 } 8768 8769 // Otherwise a node which might have a `dt-column` data attribute, or be 8770 // a child or such an element 8771 var host = $(s).closest('*[data-dt-column]'); 8772 return host.length ? 8773 [ host.data('dt-column') ] : 8774 []; 8775 }; 8776 8777 return _selector_run( 'column', selector, run, settings, opts ); 8778 }; 8779 8780 8781 var __setColumnVis = function ( settings, column, vis ) { 8782 var 8783 cols = settings.aoColumns, 8784 col = cols[ column ], 8785 data = settings.aoData, 8786 row, cells, i, ien, tr; 8787 8788 // Get 8789 if ( vis === undefined ) { 8790 return col.bVisible; 8791 } 8792 8793 // Set 8794 // No change 8795 if ( col.bVisible === vis ) { 8796 return; 8797 } 8798 8799 if ( vis ) { 8800 // Insert column 8801 // Need to decide if we should use appendChild or insertBefore 8802 var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 ); 8803 8804 for ( i=0, ien=data.length ; i<ien ; i++ ) { 8805 tr = data[i].nTr; 8806 cells = data[i].anCells; 8807 8808 if ( tr ) { 8809 // insertBefore can act like appendChild if 2nd arg is null 8810 tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); 8811 } 8812 } 8813 } 8814 else { 8815 // Remove column 8816 $( _pluck( settings.aoData, 'anCells', column ) ).detach(); 8817 } 8818 8819 // Common actions 8820 col.bVisible = vis; 8821 }; 8822 8823 8824 _api_register( 'columns()', function ( selector, opts ) { 8825 // argument shifting 8826 if ( selector === undefined ) { 8827 selector = ''; 8828 } 8829 else if ( $.isPlainObject( selector ) ) { 8830 opts = selector; 8831 selector = ''; 8832 } 8833 8834 opts = _selector_opts( opts ); 8835 8836 var inst = this.iterator( 'table', function ( settings ) { 8837 return __column_selector( settings, selector, opts ); 8838 }, 1 ); 8839 8840 // Want argument shifting here and in _row_selector? 8841 inst.selector.cols = selector; 8842 inst.selector.opts = opts; 8843 8844 return inst; 8845 } ); 8846 8847 _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { 8848 return this.iterator( 'column', function ( settings, column ) { 8849 return settings.aoColumns[column].nTh; 8850 }, 1 ); 8851 } ); 8852 8853 _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { 8854 return this.iterator( 'column', function ( settings, column ) { 8855 return settings.aoColumns[column].nTf; 8856 }, 1 ); 8857 } ); 8858 8859 _api_registerPlural( 'columns().data()', 'column().data()', function () { 8860 return this.iterator( 'column-rows', __columnData, 1 ); 8861 } ); 8862 8863 _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { 8864 return this.iterator( 'column', function ( settings, column ) { 8865 return settings.aoColumns[column].mData; 8866 }, 1 ); 8867 } ); 8868 8869 _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { 8870 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8871 return _pluck_order( settings.aoData, rows, 8872 type === 'search' ? '_aFilterData' : '_aSortData', column 8873 ); 8874 }, 1 ); 8875 } ); 8876 8877 _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { 8878 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { 8879 return _pluck_order( settings.aoData, rows, 'anCells', column ) ; 8880 }, 1 ); 8881 } ); 8882 8883 _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { 8884 var that = this; 8885 var ret = this.iterator( 'column', function ( settings, column ) { 8886 if ( vis === undefined ) { 8887 return settings.aoColumns[ column ].bVisible; 8888 } // else 8889 __setColumnVis( settings, column, vis ); 8890 } ); 8891 8892 // Group the column visibility changes 8893 if ( vis !== undefined ) { 8894 this.iterator( 'table', function ( settings ) { 8895 // Redraw the header after changes 8896 _fnDrawHead( settings, settings.aoHeader ); 8897 _fnDrawHead( settings, settings.aoFooter ); 8898 8899 // Update colspan for no records display. Child rows and extensions will use their own 8900 // listeners to do this - only need to update the empty table item here 8901 if ( ! settings.aiDisplay.length ) { 8902 $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); 8903 } 8904 8905 _fnSaveState( settings ); 8906 8907 // Second loop once the first is done for events 8908 that.iterator( 'column', function ( settings, column ) { 8909 _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); 8910 } ); 8911 8912 if ( calc === undefined || calc ) { 8913 that.columns.adjust(); 8914 } 8915 }); 8916 } 8917 8918 return ret; 8919 } ); 8920 8921 _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { 8922 return this.iterator( 'column', function ( settings, column ) { 8923 return type === 'visible' ? 8924 _fnColumnIndexToVisible( settings, column ) : 8925 column; 8926 }, 1 ); 8927 } ); 8928 8929 _api_register( 'columns.adjust()', function () { 8930 return this.iterator( 'table', function ( settings ) { 8931 _fnAdjustColumnSizing( settings ); 8932 }, 1 ); 8933 } ); 8934 8935 _api_register( 'column.index()', function ( type, idx ) { 8936 if ( this.context.length !== 0 ) { 8937 var ctx = this.context[0]; 8938 8939 if ( type === 'fromVisible' || type === 'toData' ) { 8940 return _fnVisibleToColumnIndex( ctx, idx ); 8941 } 8942 else if ( type === 'fromData' || type === 'toVisible' ) { 8943 return _fnColumnIndexToVisible( ctx, idx ); 8944 } 8945 } 8946 } ); 8947 8948 _api_register( 'column()', function ( selector, opts ) { 8949 return _selector_first( this.columns( selector, opts ) ); 8950 } ); 8951 8952 var __cell_selector = function ( settings, selector, opts ) 8953 { 8954 var data = settings.aoData; 8955 var rows = _selector_row_indexes( settings, opts ); 8956 var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); 8957 var allCells = $(_flatten( [], cells )); 8958 var row; 8959 var columns = settings.aoColumns.length; 8960 var a, i, ien, j, o, host; 8961 8962 var run = function ( s ) { 8963 var fnSelector = typeof s === 'function'; 8964 8965 if ( s === null || s === undefined || fnSelector ) { 8966 // All cells and function selectors 8967 a = []; 8968 8969 for ( i=0, ien=rows.length ; i<ien ; i++ ) { 8970 row = rows[i]; 8971 8972 for ( j=0 ; j<columns ; j++ ) { 8973 o = { 8974 row: row, 8975 column: j 8976 }; 8977 8978 if ( fnSelector ) { 8979 // Selector - function 8980 host = data[ row ]; 8981 8982 if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { 8983 a.push( o ); 8984 } 8985 } 8986 else { 8987 // Selector - all 8988 a.push( o ); 8989 } 8990 } 8991 } 8992 8993 return a; 8994 } 8995 8996 // Selector - index 8997 if ( $.isPlainObject( s ) ) { 8998 // Valid cell index and its in the array of selectable rows 8999 return s.column !== undefined && s.row !== undefined && $.inArray( s.row, rows ) !== -1 ? 9000 [s] : 9001 []; 9002 } 9003 9004 // Selector - jQuery filtered cells 9005 var jqResult = allCells 9006 .filter( s ) 9007 .map( function (i, el) { 9008 return { // use a new object, in case someone changes the values 9009 row: el._DT_CellIndex.row, 9010 column: el._DT_CellIndex.column 9011 }; 9012 } ) 9013 .toArray(); 9014 9015 if ( jqResult.length || ! s.nodeName ) { 9016 return jqResult; 9017 } 9018 9019 // Otherwise the selector is a node, and there is one last option - the 9020 // element might be a child of an element which has dt-row and dt-column 9021 // data attributes 9022 host = $(s).closest('*[data-dt-row]'); 9023 return host.length ? 9024 [ { 9025 row: host.data('dt-row'), 9026 column: host.data('dt-column') 9027 } ] : 9028 []; 9029 }; 9030 9031 return _selector_run( 'cell', selector, run, settings, opts ); 9032 }; 9033 9034 9035 9036 9037 _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { 9038 // Argument shifting 9039 if ( $.isPlainObject( rowSelector ) ) { 9040 // Indexes 9041 if ( rowSelector.row === undefined ) { 9042 // Selector options in first parameter 9043 opts = rowSelector; 9044 rowSelector = null; 9045 } 9046 else { 9047 // Cell index objects in first parameter 9048 opts = columnSelector; 9049 columnSelector = null; 9050 } 9051 } 9052 if ( $.isPlainObject( columnSelector ) ) { 9053 opts = columnSelector; 9054 columnSelector = null; 9055 } 9056 9057 // Cell selector 9058 if ( columnSelector === null || columnSelector === undefined ) { 9059 return this.iterator( 'table', function ( settings ) { 9060 return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); 9061 } ); 9062 } 9063 9064 // The default built in options need to apply to row and columns 9065 var internalOpts = opts ? { 9066 page: opts.page, 9067 order: opts.order, 9068 search: opts.search 9069 } : {}; 9070 9071 // Row + column selector 9072 var columns = this.columns( columnSelector, internalOpts ); 9073 var rows = this.rows( rowSelector, internalOpts ); 9074 var i, ien, j, jen; 9075 9076 var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { 9077 var a = []; 9078 9079 for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { 9080 for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { 9081 a.push( { 9082 row: rows[idx][i], 9083 column: columns[idx][j] 9084 } ); 9085 } 9086 } 9087 9088 return a; 9089 }, 1 ); 9090 9091 // There is currently only one extension which uses a cell selector extension 9092 // It is a _major_ performance drag to run this if it isn't needed, so this is 9093 // an extension specific check at the moment 9094 var cells = opts && opts.selected ? 9095 this.cells( cellsNoOpts, opts ) : 9096 cellsNoOpts; 9097 9098 $.extend( cells.selector, { 9099 cols: columnSelector, 9100 rows: rowSelector, 9101 opts: opts 9102 } ); 9103 9104 return cells; 9105 } ); 9106 9107 9108 _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { 9109 return this.iterator( 'cell', function ( settings, row, column ) { 9110 var data = settings.aoData[ row ]; 9111 9112 return data && data.anCells ? 9113 data.anCells[ column ] : 9114 undefined; 9115 }, 1 ); 9116 } ); 9117 9118 9119 _api_register( 'cells().data()', function () { 9120 return this.iterator( 'cell', function ( settings, row, column ) { 9121 return _fnGetCellData( settings, row, column ); 9122 }, 1 ); 9123 } ); 9124 9125 9126 _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { 9127 type = type === 'search' ? '_aFilterData' : '_aSortData'; 9128 9129 return this.iterator( 'cell', function ( settings, row, column ) { 9130 return settings.aoData[ row ][ type ][ column ]; 9131 }, 1 ); 9132 } ); 9133 9134 9135 _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { 9136 return this.iterator( 'cell', function ( settings, row, column ) { 9137 return _fnGetCellData( settings, row, column, type ); 9138 }, 1 ); 9139 } ); 9140 9141 9142 _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { 9143 return this.iterator( 'cell', function ( settings, row, column ) { 9144 return { 9145 row: row, 9146 column: column, 9147 columnVisible: _fnColumnIndexToVisible( settings, column ) 9148 }; 9149 }, 1 ); 9150 } ); 9151 9152 9153 _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { 9154 return this.iterator( 'cell', function ( settings, row, column ) { 9155 _fnInvalidate( settings, row, src, column ); 9156 } ); 9157 } ); 9158 9159 9160 9161 _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { 9162 return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); 9163 } ); 9164 9165 9166 _api_register( 'cell().data()', function ( data ) { 9167 var ctx = this.context; 9168 var cell = this[0]; 9169 9170 if ( data === undefined ) { 9171 // Get 9172 return ctx.length && cell.length ? 9173 _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : 9174 undefined; 9175 } 9176 9177 // Set 9178 _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); 9179 _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); 9180 9181 return this; 9182 } ); 9183 9184 9185 9186 /** 9187 * Get current ordering (sorting) that has been applied to the table. 9188 * 9189 * @returns {array} 2D array containing the sorting information for the first 9190 * table in the current context. Each element in the parent array represents 9191 * a column being sorted upon (i.e. multi-sorting with two columns would have 9192 * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is 9193 * the column index that the sorting condition applies to, the second is the 9194 * direction of the sort (`desc` or `asc`) and, optionally, the third is the 9195 * index of the sorting order from the `column.sorting` initialisation array. 9196 *//** 9197 * Set the ordering for the table. 9198 * 9199 * @param {integer} order Column index to sort upon. 9200 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) 9201 * @returns {DataTables.Api} this 9202 *//** 9203 * Set the ordering for the table. 9204 * 9205 * @param {array} order 1D array of sorting information to be applied. 9206 * @param {array} [...] Optional additional sorting conditions 9207 * @returns {DataTables.Api} this 9208 *//** 9209 * Set the ordering for the table. 9210 * 9211 * @param {array} order 2D array of sorting information to be applied. 9212 * @returns {DataTables.Api} this 9213 */ 9214 _api_register( 'order()', function ( order, dir ) { 9215 var ctx = this.context; 9216 9217 if ( order === undefined ) { 9218 // get 9219 return ctx.length !== 0 ? 9220 ctx[0].aaSorting : 9221 undefined; 9222 } 9223 9224 // set 9225 if ( typeof order === 'number' ) { 9226 // Simple column / direction passed in 9227 order = [ [ order, dir ] ]; 9228 } 9229 else if ( order.length && ! Array.isArray( order[0] ) ) { 9230 // Arguments passed in (list of 1D arrays) 9231 order = Array.prototype.slice.call( arguments ); 9232 } 9233 // otherwise a 2D array was passed in 9234 9235 return this.iterator( 'table', function ( settings ) { 9236 settings.aaSorting = order.slice(); 9237 } ); 9238 } ); 9239 9240 9241 /** 9242 * Attach a sort listener to an element for a given column 9243 * 9244 * @param {node|jQuery|string} node Identifier for the element(s) to attach the 9245 * listener to. This can take the form of a single DOM node, a jQuery 9246 * collection of nodes or a jQuery selector which will identify the node(s). 9247 * @param {integer} column the column that a click on this node will sort on 9248 * @param {function} [callback] callback function when sort is run 9249 * @returns {DataTables.Api} this 9250 */ 9251 _api_register( 'order.listener()', function ( node, column, callback ) { 9252 return this.iterator( 'table', function ( settings ) { 9253 _fnSortAttachListener( settings, node, column, callback ); 9254 } ); 9255 } ); 9256 9257 9258 _api_register( 'order.fixed()', function ( set ) { 9259 if ( ! set ) { 9260 var ctx = this.context; 9261 var fixed = ctx.length ? 9262 ctx[0].aaSortingFixed : 9263 undefined; 9264 9265 return Array.isArray( fixed ) ? 9266 { pre: fixed } : 9267 fixed; 9268 } 9269 9270 return this.iterator( 'table', function ( settings ) { 9271 settings.aaSortingFixed = $.extend( true, {}, set ); 9272 } ); 9273 } ); 9274 9275 9276 // Order by the selected column(s) 9277 _api_register( [ 9278 'columns().order()', 9279 'column().order()' 9280 ], function ( dir ) { 9281 var that = this; 9282 9283 return this.iterator( 'table', function ( settings, i ) { 9284 var sort = []; 9285 9286 $.each( that[i], function (j, col) { 9287 sort.push( [ col, dir ] ); 9288 } ); 9289 9290 settings.aaSorting = sort; 9291 } ); 9292 } ); 9293 9294 9295 9296 _api_register( 'search()', function ( input, regex, smart, caseInsen ) { 9297 var ctx = this.context; 9298 9299 if ( input === undefined ) { 9300 // get 9301 return ctx.length !== 0 ? 9302 ctx[0].oPreviousSearch.sSearch : 9303 undefined; 9304 } 9305 9306 // set 9307 return this.iterator( 'table', function ( settings ) { 9308 if ( ! settings.oFeatures.bFilter ) { 9309 return; 9310 } 9311 9312 _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, { 9313 "sSearch": input+"", 9314 "bRegex": regex === null ? false : regex, 9315 "bSmart": smart === null ? true : smart, 9316 "bCaseInsensitive": caseInsen === null ? true : caseInsen 9317 } ), 1 ); 9318 } ); 9319 } ); 9320 9321 9322 _api_registerPlural( 9323 'columns().search()', 9324 'column().search()', 9325 function ( input, regex, smart, caseInsen ) { 9326 return this.iterator( 'column', function ( settings, column ) { 9327 var preSearch = settings.aoPreSearchCols; 9328 9329 if ( input === undefined ) { 9330 // get 9331 return preSearch[ column ].sSearch; 9332 } 9333 9334 // set 9335 if ( ! settings.oFeatures.bFilter ) { 9336 return; 9337 } 9338 9339 $.extend( preSearch[ column ], { 9340 "sSearch": input+"", 9341 "bRegex": regex === null ? false : regex, 9342 "bSmart": smart === null ? true : smart, 9343 "bCaseInsensitive": caseInsen === null ? true : caseInsen 9344 } ); 9345 9346 _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); 9347 } ); 9348 } 9349 ); 9350 9351 /* 9352 * State API methods 9353 */ 9354 9355 _api_register( 'state()', function () { 9356 return this.context.length ? 9357 this.context[0].oSavedState : 9358 null; 9359 } ); 9360 9361 9362 _api_register( 'state.clear()', function () { 9363 return this.iterator( 'table', function ( settings ) { 9364 // Save an empty object 9365 settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); 9366 } ); 9367 } ); 9368 9369 9370 _api_register( 'state.loaded()', function () { 9371 return this.context.length ? 9372 this.context[0].oLoadedState : 9373 null; 9374 } ); 9375 9376 9377 _api_register( 'state.save()', function () { 9378 return this.iterator( 'table', function ( settings ) { 9379 _fnSaveState( settings ); 9380 } ); 9381 } ); 9382 9383 9384 9385 /** 9386 * Set the jQuery or window object to be used by DataTables 9387 * 9388 * @param {*} module Library / container object 9389 * @param {string} type Library or container type `lib` or `win`. 9390 */ 9391 DataTable.use = function (module, type) { 9392 if (type === 'lib' || module.fn) { 9393 $ = module; 9394 } 9395 else if (type == 'win' || module.document) { 9396 window = module; 9397 document = module.document; 9398 } 9399 } 9400 9401 /** 9402 * CommonJS factory function pass through. This will check if the arguments 9403 * given are a window object or a jQuery object. If so they are set 9404 * accordingly. 9405 * @param {*} root Window 9406 * @param {*} jq jQUery 9407 * @returns {boolean} Indicator 9408 */ 9409 DataTable.factory = function (root, jq) { 9410 var is = false; 9411 9412 // Test if the first parameter is a window object 9413 if (root && root.document) { 9414 window = root; 9415 document = root.document; 9416 } 9417 9418 // Test if the second parameter is a jQuery object 9419 if (jq && jq.fn && jq.fn.jquery) { 9420 $ = jq; 9421 is = true; 9422 } 9423 9424 return is; 9425 } 9426 9427 /** 9428 * Provide a common method for plug-ins to check the version of DataTables being 9429 * used, in order to ensure compatibility. 9430 * 9431 * @param {string} version Version string to check for, in the format "X.Y.Z". 9432 * Note that the formats "X" and "X.Y" are also acceptable. 9433 * @returns {boolean} true if this version of DataTables is greater or equal to 9434 * the required version, or false if this version of DataTales is not 9435 * suitable 9436 * @static 9437 * @dtopt API-Static 9438 * 9439 * @example 9440 * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); 9441 */ 9442 DataTable.versionCheck = DataTable.fnVersionCheck = function( version ) 9443 { 9444 var aThis = DataTable.version.split('.'); 9445 var aThat = version.split('.'); 9446 var iThis, iThat; 9447 9448 for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { 9449 iThis = parseInt( aThis[i], 10 ) || 0; 9450 iThat = parseInt( aThat[i], 10 ) || 0; 9451 9452 // Parts are the same, keep comparing 9453 if (iThis === iThat) { 9454 continue; 9455 } 9456 9457 // Parts are different, return immediately 9458 return iThis > iThat; 9459 } 9460 9461 return true; 9462 }; 9463 9464 9465 /** 9466 * Check if a `<table>` node is a DataTable table already or not. 9467 * 9468 * @param {node|jquery|string} table Table node, jQuery object or jQuery 9469 * selector for the table to test. Note that if more than more than one 9470 * table is passed on, only the first will be checked 9471 * @returns {boolean} true the table given is a DataTable, or false otherwise 9472 * @static 9473 * @dtopt API-Static 9474 * 9475 * @example 9476 * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { 9477 * $('#example').dataTable(); 9478 * } 9479 */ 9480 DataTable.isDataTable = DataTable.fnIsDataTable = function ( table ) 9481 { 9482 var t = $(table).get(0); 9483 var is = false; 9484 9485 if ( table instanceof DataTable.Api ) { 9486 return true; 9487 } 9488 9489 $.each( DataTable.settings, function (i, o) { 9490 var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; 9491 var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; 9492 9493 if ( o.nTable === t || head === t || foot === t ) { 9494 is = true; 9495 } 9496 } ); 9497 9498 return is; 9499 }; 9500 9501 9502 /** 9503 * Get all DataTable tables that have been initialised - optionally you can 9504 * select to get only currently visible tables. 9505 * 9506 * @param {boolean} [visible=false] Flag to indicate if you want all (default) 9507 * or visible tables only. 9508 * @returns {array} Array of `table` nodes (not DataTable instances) which are 9509 * DataTables 9510 * @static 9511 * @dtopt API-Static 9512 * 9513 * @example 9514 * $.each( $.fn.dataTable.tables(true), function () { 9515 * $(table).DataTable().columns.adjust(); 9516 * } ); 9517 */ 9518 DataTable.tables = DataTable.fnTables = function ( visible ) 9519 { 9520 var api = false; 9521 9522 if ( $.isPlainObject( visible ) ) { 9523 api = visible.api; 9524 visible = visible.visible; 9525 } 9526 9527 var a = $.map( DataTable.settings, function (o) { 9528 if ( !visible || (visible && $(o.nTable).is(':visible')) ) { 9529 return o.nTable; 9530 } 9531 } ); 9532 9533 return api ? 9534 new _Api( a ) : 9535 a; 9536 }; 9537 9538 9539 /** 9540 * Convert from camel case parameters to Hungarian notation. This is made public 9541 * for the extensions to provide the same ability as DataTables core to accept 9542 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase 9543 * parameters. 9544 * 9545 * @param {object} src The model object which holds all parameters that can be 9546 * mapped. 9547 * @param {object} user The object to convert from camel case to Hungarian. 9548 * @param {boolean} force When set to `true`, properties which already have a 9549 * Hungarian value in the `user` object will be overwritten. Otherwise they 9550 * won't be. 9551 */ 9552 DataTable.camelToHungarian = _fnCamelToHungarian; 9553 9554 9555 9556 /** 9557 * 9558 */ 9559 _api_register( '$()', function ( selector, opts ) { 9560 var 9561 rows = this.rows( opts ).nodes(), // Get all rows 9562 jqRows = $(rows); 9563 9564 return $( [].concat( 9565 jqRows.filter( selector ).toArray(), 9566 jqRows.find( selector ).toArray() 9567 ) ); 9568 } ); 9569 9570 9571 // jQuery functions to operate on the tables 9572 $.each( [ 'on', 'one', 'off' ], function (i, key) { 9573 _api_register( key+'()', function ( /* event, handler */ ) { 9574 var args = Array.prototype.slice.call(arguments); 9575 9576 // Add the `dt` namespace automatically if it isn't already present 9577 args[0] = $.map( args[0].split( /\s/ ), function ( e ) { 9578 return ! e.match(/\.dt\b/) ? 9579 e+'.dt' : 9580 e; 9581 } ).join( ' ' ); 9582 9583 var inst = $( this.tables().nodes() ); 9584 inst[key].apply( inst, args ); 9585 return this; 9586 } ); 9587 } ); 9588 9589 9590 _api_register( 'clear()', function () { 9591 return this.iterator( 'table', function ( settings ) { 9592 _fnClearTable( settings ); 9593 } ); 9594 } ); 9595 9596 9597 _api_register( 'settings()', function () { 9598 return new _Api( this.context, this.context ); 9599 } ); 9600 9601 9602 _api_register( 'init()', function () { 9603 var ctx = this.context; 9604 return ctx.length ? ctx[0].oInit : null; 9605 } ); 9606 9607 9608 _api_register( 'data()', function () { 9609 return this.iterator( 'table', function ( settings ) { 9610 return _pluck( settings.aoData, '_aData' ); 9611 } ).flatten(); 9612 } ); 9613 9614 9615 _api_register( 'destroy()', function ( remove ) { 9616 remove = remove || false; 9617 9618 return this.iterator( 'table', function ( settings ) { 9619 var classes = settings.oClasses; 9620 var table = settings.nTable; 9621 var tbody = settings.nTBody; 9622 var thead = settings.nTHead; 9623 var tfoot = settings.nTFoot; 9624 var jqTable = $(table); 9625 var jqTbody = $(tbody); 9626 var jqWrapper = $(settings.nTableWrapper); 9627 var rows = $.map( settings.aoData, function (r) { return r.nTr; } ); 9628 var i, ien; 9629 9630 // Flag to note that the table is currently being destroyed - no action 9631 // should be taken 9632 settings.bDestroying = true; 9633 9634 // Fire off the destroy callbacks for plug-ins etc 9635 _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] ); 9636 9637 // If not being removed from the document, make all columns visible 9638 if ( ! remove ) { 9639 new _Api( settings ).columns().visible( true ); 9640 } 9641 9642 // Blitz all `DT` namespaced events (these are internal events, the 9643 // lowercase, `dt` events are user subscribed and they are responsible 9644 // for removing them 9645 jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); 9646 $(window).off('.DT-'+settings.sInstance); 9647 9648 // When scrolling we had to break the table up - restore it 9649 if ( table != thead.parentNode ) { 9650 jqTable.children('thead').detach(); 9651 jqTable.append( thead ); 9652 } 9653 9654 if ( tfoot && table != tfoot.parentNode ) { 9655 jqTable.children('tfoot').detach(); 9656 jqTable.append( tfoot ); 9657 } 9658 9659 settings.aaSorting = []; 9660 settings.aaSortingFixed = []; 9661 _fnSortingClasses( settings ); 9662 9663 $( rows ).removeClass( settings.asStripeClasses.join(' ') ); 9664 9665 $('th, td', thead).removeClass( classes.sSortable+' '+ 9666 classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone 9667 ); 9668 9669 // Add the TR elements back into the table in their original order 9670 jqTbody.children().detach(); 9671 jqTbody.append( rows ); 9672 9673 var orig = settings.nTableWrapper.parentNode; 9674 9675 // Remove the DataTables generated nodes, events and classes 9676 var removedMethod = remove ? 'remove' : 'detach'; 9677 jqTable[ removedMethod ](); 9678 jqWrapper[ removedMethod ](); 9679 9680 // If we need to reattach the table to the document 9681 if ( ! remove && orig ) { 9682 // insertBefore acts like appendChild if !arg[1] 9683 orig.insertBefore( table, settings.nTableReinsertBefore ); 9684 9685 // Restore the width of the original table - was read from the style property, 9686 // so we can restore directly to that 9687 jqTable 9688 .css( 'width', settings.sDestroyWidth ) 9689 .removeClass( classes.sTable ); 9690 9691 // If the were originally stripe classes - then we add them back here. 9692 // Note this is not fool proof (for example if not all rows had stripe 9693 // classes - but it's a good effort without getting carried away 9694 ien = settings.asDestroyStripes.length; 9695 9696 if ( ien ) { 9697 jqTbody.children().each( function (i) { 9698 $(this).addClass( settings.asDestroyStripes[i % ien] ); 9699 } ); 9700 } 9701 } 9702 9703 /* Remove the settings object from the settings array */ 9704 var idx = $.inArray( settings, DataTable.settings ); 9705 if ( idx !== -1 ) { 9706 DataTable.settings.splice( idx, 1 ); 9707 } 9708 } ); 9709 } ); 9710 9711 9712 // Add the `every()` method for rows, columns and cells in a compact form 9713 $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { 9714 _api_register( type+'s().every()', function ( fn ) { 9715 var opts = this.selector.opts; 9716 var api = this; 9717 9718 return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) { 9719 // Rows and columns: 9720 // arg1 - index 9721 // arg2 - table counter 9722 // arg3 - loop counter 9723 // arg4 - undefined 9724 // Cells: 9725 // arg1 - row index 9726 // arg2 - column index 9727 // arg3 - table counter 9728 // arg4 - loop counter 9729 fn.call( 9730 api[ type ]( 9731 arg1, 9732 type==='cell' ? arg2 : opts, 9733 type==='cell' ? opts : undefined 9734 ), 9735 arg1, arg2, arg3, arg4 9736 ); 9737 } ); 9738 } ); 9739 } ); 9740 9741 9742 // i18n method for extensions to be able to use the language object from the 9743 // DataTable 9744 _api_register( 'i18n()', function ( token, def, plural ) { 9745 var ctx = this.context[0]; 9746 var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); 9747 9748 if ( resolved === undefined ) { 9749 resolved = def; 9750 } 9751 9752 if ( plural !== undefined && $.isPlainObject( resolved ) ) { 9753 resolved = resolved[ plural ] !== undefined ? 9754 resolved[ plural ] : 9755 resolved._; 9756 } 9757 9758 return resolved.replace( '%d', plural ); // nb: plural might be undefined, 9759 } ); 9760 /** 9761 * Version string for plug-ins to check compatibility. Allowed format is 9762 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used 9763 * only for non-release builds. See http://semver.org/ for more information. 9764 * @member 9765 * @type string 9766 * @default Version number 9767 */ 9768 DataTable.version = "1.13.4"; 9769 9770 /** 9771 * Private data store, containing all of the settings objects that are 9772 * created for the tables on a given page. 9773 * 9774 * Note that the `DataTable.settings` object is aliased to 9775 * `jQuery.fn.dataTableExt` through which it may be accessed and 9776 * manipulated, or `jQuery.fn.dataTable.settings`. 9777 * @member 9778 * @type array 9779 * @default [] 9780 * @private 9781 */ 9782 DataTable.settings = []; 9783 9784 /** 9785 * Object models container, for the various models that DataTables has 9786 * available to it. These models define the objects that are used to hold 9787 * the active state and configuration of the table. 9788 * @namespace 9789 */ 9790 DataTable.models = {}; 9791 9792 9793 9794 /** 9795 * Template object for the way in which DataTables holds information about 9796 * search information for the global filter and individual column filters. 9797 * @namespace 9798 */ 9799 DataTable.models.oSearch = { 9800 /** 9801 * Flag to indicate if the filtering should be case insensitive or not 9802 * @type boolean 9803 * @default true 9804 */ 9805 "bCaseInsensitive": true, 9806 9807 /** 9808 * Applied search term 9809 * @type string 9810 * @default <i>Empty string</i> 9811 */ 9812 "sSearch": "", 9813 9814 /** 9815 * Flag to indicate if the search term should be interpreted as a 9816 * regular expression (true) or not (false) and therefore and special 9817 * regex characters escaped. 9818 * @type boolean 9819 * @default false 9820 */ 9821 "bRegex": false, 9822 9823 /** 9824 * Flag to indicate if DataTables is to use its smart filtering or not. 9825 * @type boolean 9826 * @default true 9827 */ 9828 "bSmart": true, 9829 9830 /** 9831 * Flag to indicate if DataTables should only trigger a search when 9832 * the return key is pressed. 9833 * @type boolean 9834 * @default false 9835 */ 9836 "return": false 9837 }; 9838 9839 9840 9841 9842 /** 9843 * Template object for the way in which DataTables holds information about 9844 * each individual row. This is the object format used for the settings 9845 * aoData array. 9846 * @namespace 9847 */ 9848 DataTable.models.oRow = { 9849 /** 9850 * TR element for the row 9851 * @type node 9852 * @default null 9853 */ 9854 "nTr": null, 9855 9856 /** 9857 * Array of TD elements for each row. This is null until the row has been 9858 * created.
9859 * @type array nodes 9860 * @default [] 9861 */ 9862 "anCells": null, 9863 9864 /** 9865 * Data object from the original data source for the row. This is either 9866 * an array if using the traditional form of DataTables, or an object if 9867 * using mData options. The exact type will depend on the passed in 9868 * data from the data source, or will be an array if using DOM a data 9869 * source. 9870 * @type array|object 9871 * @default [] 9872 */ 9873 "_aData": [], 9874 9875 /** 9876 * Sorting data cache - this array is ostensibly the same length as the 9877 * number of columns (although each index is generated only as it is 9878 * needed), and holds the data that is used for sorting each column in the 9879 * row. We do this cache generation at the start of the sort in order that 9880 * the formatting of the sort data need be done only once for each cell 9881 * per sort. This array should not be read from or written to by anything 9882 * other than the master sorting methods. 9883 * @type array 9884 * @default null 9885 * @private 9886 */ 9887 "_aSortData": null, 9888 9889 /** 9890 * Per cell filtering data cache. As per the sort data cache, used to 9891 * increase the performance of the filtering in DataTables 9892 * @type array 9893 * @default null 9894 * @private 9895 */ 9896 "_aFilterData": null, 9897 9898 /** 9899 * Filtering data cache. This is the same as the cell filtering cache, but 9900 * in this case a string rather than an array. This is easily computed with 9901 * a join on `_aFilterData`, but is provided as a cache so the join isn't 9902 * needed on every search (memory traded for performance) 9903 * @type array 9904 * @default null 9905 * @private 9906 */ 9907 "_sFilterRow": null, 9908 9909 /** 9910 * Cache of the class name that DataTables has applied to the row, so we 9911 * can quickly look at this variable rather than needing to do a DOM check 9912 * on className for the nTr property. 9913 * @type string 9914 * @default <i>Empty string</i> 9915 * @private 9916 */ 9917 "_sRowStripe": "", 9918 9919 /** 9920 * Denote if the original data source was from the DOM, or the data source 9921 * object. This is used for invalidating data, so DataTables can 9922 * automatically read data from the original source, unless uninstructed 9923 * otherwise. 9924 * @type string 9925 * @default null 9926 * @private 9927 */ 9928 "src": null, 9929 9930 /** 9931 * Index in the aoData array. This saves an indexOf lookup when we have the 9932 * object, but want to know the index 9933 * @type integer 9934 * @default -1 9935 * @private 9936 */ 9937 "idx": -1 9938 }; 9939 9940 9941 /** 9942 * Template object for the column information object in DataTables. This object 9943 * is held in the settings aoColumns array and contains all the information that 9944 * DataTables needs about each individual column. 9945 * 9946 * Note that this object is related to {@link DataTable.defaults.column} 9947 * but this one is the internal data store for DataTables's cache of columns. 9948 * It should NOT be manipulated outside of DataTables. Any configuration should 9949 * be done through the initialisation options. 9950 * @namespace 9951 */ 9952 DataTable.models.oColumn = { 9953 /** 9954 * Column index. This could be worked out on-the-fly with $.inArray, but it 9955 * is faster to just hold it as a variable 9956 * @type integer 9957 * @default null 9958 */ 9959 "idx": null, 9960 9961 /** 9962 * A list of the columns that sorting should occur on when this column 9963 * is sorted. That this property is an array allows multi-column sorting 9964 * to be defined for a column (for example first name / last name columns 9965 * would benefit from this). The values are integers pointing to the 9966 * columns to be sorted on (typically it will be a single integer pointing 9967 * at itself, but that doesn't need to be the case). 9968 * @type array 9969 */ 9970 "aDataSort": null, 9971 9972 /** 9973 * Define the sorting directions that are applied to the column, in sequence 9974 * as the column is repeatedly sorted upon - i.e. the first value is used 9975 * as the sorting direction when the column if first sorted (clicked on). 9976 * Sort it again (click again) and it will move on to the next index. 9977 * Repeat until loop. 9978 * @type array 9979 */ 9980 "asSorting": null, 9981 9982 /** 9983 * Flag to indicate if the column is searchable, and thus should be included 9984 * in the filtering or not. 9985 * @type boolean 9986 */ 9987 "bSearchable": null, 9988 9989 /** 9990 * Flag to indicate if the column is sortable or not. 9991 * @type boolean 9992 */ 9993 "bSortable": null, 9994 9995 /** 9996 * Flag to indicate if the column is currently visible in the table or not 9997 * @type boolean 9998 */ 9999 "bVisible": null, 10000 10001 /** 10002 * Store for manual type assignment using the `column.type` option. This 10003 * is held in store so we can manipulate the column's `sType` property. 10004 * @type string 10005 * @default null 10006 * @private 10007 */ 10008 "_sManualType": null, 10009 10010 /** 10011 * Flag to indicate if HTML5 data attributes should be used as the data 10012 * source for filtering or sorting. True is either are. 10013 * @type boolean 10014 * @default false 10015 * @private 10016 */ 10017 "_bAttrSrc": false, 10018 10019 /** 10020 * Developer definable function that is called whenever a cell is created (Ajax source, 10021 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 10022 * allowing you to modify the DOM element (add background colour for example) when the 10023 * element is available. 10024 * @type function 10025 * @param {element} nTd The TD node that has been created 10026 * @param {*} sData The Data for the cell 10027 * @param {array|object} oData The data for the whole row 10028 * @param {int} iRow The row index for the aoData data store 10029 * @default null 10030 */ 10031 "fnCreatedCell": null, 10032 10033 /** 10034 * Function to get data from a cell in a column. You should <b>never</b> 10035 * access data directly through _aData internally in DataTables - always use 10036 * the method attached to this property. It allows mData to function as 10037 * required. This function is automatically assigned by the column 10038 * initialisation method 10039 * @type function 10040 * @param {array|object} oData The data array/object for the array 10041 * (i.e. aoData[]._aData) 10042 * @param {string} sSpecific The specific data type you want to get - 10043 * 'display', 'type' 'filter' 'sort' 10044 * @returns {*} The data for the cell from the given row's data 10045 * @default null 10046 */ 10047 "fnGetData": null, 10048 10049 /** 10050 * Function to set data for a cell in the column. You should <b>never</b> 10051 * set the data directly to _aData internally in DataTables - always use 10052 * this method. It allows mData to function as required. This function 10053 * is automatically assigned by the column initialisation method 10054 * @type function 10055 * @param {array|object} oData The data array/object for the array 10056 * (i.e. aoData[]._aData) 10057 * @param {*} sValue Value to set 10058 * @default null 10059 */ 10060 "fnSetData": null, 10061 10062 /** 10063 * Property to read the value for the cells in the column from the data 10064 * source array / object. If null, then the default content is used, if a 10065 * function is given then the return from the function is used. 10066 * @type function|int|string|null 10067 * @default null 10068 */ 10069 "mData": null, 10070 10071 /** 10072 * Partner property to mData which is used (only when defined) to get 10073 * the data - i.e. it is basically the same as mData, but without the 10074 * 'set' option, and also the data fed to it is the result from mData. 10075 * This is the rendering method to match the data method of mData. 10076 * @type function|int|string|null 10077 * @default null 10078 */ 10079 "mRender": null, 10080 10081 /** 10082 * Unique header TH/TD element for this column - this is what the sorting 10083 * listener is attached to (if sorting is enabled.) 10084 * @type node 10085 * @default null 10086 */ 10087 "nTh": null, 10088 10089 /** 10090 * Unique footer TH/TD element for this column (if there is one). Not used
10091 * in DataTables as such, but can be used for plug-ins to reference the 10092 * footer for each column. 10093 * @type node 10094 * @default null 10095 */ 10096 "nTf": null, 10097 10098 /** 10099 * The class to apply to all TD elements in the table's TBODY for the column 10100 * @type string 10101 * @default null 10102 */ 10103 "sClass": null, 10104 10105 /** 10106 * When DataTables calculates the column widths to assign to each column, 10107 * it finds the longest string in each column and then constructs a 10108 * temporary table and reads the widths from that. The problem with this 10109 * is that "mmm" is much wider then "iiii", but the latter is a longer 10110 * string - thus the calculation can go wrong (doing it properly and putting 10111 * it into an DOM object and measuring that is horribly(!) slow). Thus as 10112 * a "work around" we provide this option. It will append its value to the 10113 * text that is found to be the longest string for the column - i.e. padding. 10114 * @type string 10115 */ 10116 "sContentPadding": null, 10117 10118 /** 10119 * Allows a default value to be given for a column's data, and will be used 10120 * whenever a null data source is encountered (this can be because mData 10121 * is set to null, or because the data source itself is null). 10122 * @type string 10123 * @default null 10124 */ 10125 "sDefaultContent": null, 10126 10127 /** 10128 * Name for the column, allowing reference to the column by name as well as 10129 * by index (needs a lookup to work by name). 10130 * @type string 10131 */ 10132 "sName": null, 10133 10134 /** 10135 * Custom sorting data type - defines which of the available plug-ins in 10136 * afnSortData the custom sorting will use - if any is defined. 10137 * @type string 10138 * @default std 10139 */ 10140 "sSortDataType": 'std', 10141 10142 /** 10143 * Class to be applied to the header element when sorting on this column 10144 * @type string 10145 * @default null 10146 */ 10147 "sSortingClass": null, 10148 10149 /** 10150 * Class to be applied to the header element when sorting on this column - 10151 * when jQuery UI theming is used. 10152 * @type string 10153 * @default null 10154 */ 10155 "sSortingClassJUI": null, 10156 10157 /** 10158 * Title of the column - what is seen in the TH element (nTh). 10159 * @type string 10160 */ 10161 "sTitle": null, 10162 10163 /** 10164 * Column sorting and filtering type 10165 * @type string 10166 * @default null 10167 */ 10168 "sType": null, 10169 10170 /** 10171 * Width of the column 10172 * @type string 10173 * @default null 10174 */ 10175 "sWidth": null, 10176 10177 /** 10178 * Width of the column when it was first "encountered" 10179 * @type string 10180 * @default null 10181 */ 10182 "sWidthOrig": null 10183 }; 10184 10185 10186 /* 10187 * Developer note: The properties of the object below are given in Hungarian 10188 * notation, that was used as the interface for DataTables prior to v1.10, however 10189 * from v1.10 onwards the primary interface is camel case. In order to avoid 10190 * breaking backwards compatibility utterly with this change, the Hungarian 10191 * version is still, internally the primary interface, but is is not documented 10192 * - hence the @name tags in each doc comment. This allows a Javascript function 10193 * to create a map from Hungarian notation to camel case (going the other direction 10194 * would require each property to be listed, which would add around 3K to the size 10195 * of DataTables, while this method is about a 0.5K hit). 10196 * 10197 * Ultimately this does pave the way for Hungarian notation to be dropped 10198 * completely, but that is a massive amount of work and will break current 10199 * installs (therefore is on-hold until v2). 10200 */ 10201 10202 /** 10203 * Initialisation options that can be given to DataTables at initialisation 10204 * time. 10205 * @namespace 10206 */ 10207 DataTable.defaults = { 10208 /** 10209 * An array of data to use for the table, passed in at initialisation which 10210 * will be used in preference to any data which is already in the DOM. This is 10211 * particularly useful for constructing tables purely in Javascript, for 10212 * example with a custom Ajax call. 10213 * @type array 10214 * @default null 10215 * 10216 * @dtopt Option 10217 * @name DataTable.defaults.data 10218 * 10219 * @example 10220 * // Using a 2D array data source 10221 * $(document).ready( function () { 10222 * $('#example').dataTable( { 10223 * "data": [ 10224 * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], 10225 * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], 10226 * ], 10227 * "columns": [ 10228 * { "title": "Engine" }, 10229 * { "title": "Browser" }, 10230 * { "title": "Platform" }, 10231 * { "title": "Version" }, 10232 * { "title": "Grade" } 10233 * ] 10234 * } ); 10235 * } ); 10236 * 10237 * @example 10238 * // Using an array of objects as a data source (`data`) 10239 * $(document).ready( function () { 10240 * $('#example').dataTable( { 10241 * "data": [ 10242 * {
10243 * "engine": "Trident", 10244 * "browser": "Internet Explorer 4.0", 10245 * "platform": "Win 95+", 10246 * "version": 4, 10247 * "grade": "X" 10248 * }, 10249 * { 10250 * "engine": "Trident", 10251 * "browser": "Internet Explorer 5.0", 10252 * "platform": "Win 95+", 10253 * "version": 5, 10254 * "grade": "C" 10255 * } 10256 * ], 10257 * "columns": [ 10258 * { "title": "Engine", "data": "engine" }, 10259 * { "title": "Browser", "data": "browser" }, 10260 * { "title": "Platform", "data": "platform" }, 10261 * { "title": "Version", "data": "version" }, 10262 * { "title": "Grade", "data": "grade" } 10263 * ] 10264 * } ); 10265 * } ); 10266 */ 10267 "aaData": null, 10268 10269 10270 /** 10271 * If ordering is enabled, then DataTables will perform a first pass sort on 10272 * initialisation. You can define which column(s) the sort is performed 10273 * upon, and the sorting direction, with this variable. The `sorting` array 10274 * should contain an array for each column to be sorted initially containing 10275 * the column's index and a direction string ('asc' or 'desc'). 10276 * @type array 10277 * @default [[0,'asc']] 10278 * 10279 * @dtopt Option 10280 * @name DataTable.defaults.order 10281 * 10282 * @example 10283 * // Sort by 3rd column first, and then 4th column 10284 * $(document).ready( function() { 10285 * $('#example').dataTable( { 10286 * "order": [[2,'asc'], [3,'desc']] 10287 * } ); 10288 * } ); 10289 * 10290 * // No initial sorting 10291 * $(document).ready( function() { 10292 * $('#example').dataTable( { 10293 * "order": [] 10294 * } ); 10295 * } ); 10296 */ 10297 "aaSorting": [[0,'asc']], 10298 10299 10300 /** 10301 * This parameter is basically identical to the `sorting` parameter, but 10302 * cannot be overridden by user interaction with the table. What this means 10303 * is that you could have a column (visible or hidden) which the sorting 10304 * will always be forced on first - any sorting after that (from the user) 10305 * will then be performed as required. This can be useful for grouping rows 10306 * together. 10307 * @type array 10308 * @default null 10309 * 10310 * @dtopt Option 10311 * @name DataTable.defaults.orderFixed 10312 * 10313 * @example 10314 * $(document).ready( function() { 10315 * $('#example').dataTable( { 10316 * "orderFixed": [[0,'asc']] 10317 * } ); 10318 * } ) 10319 */ 10320 "aaSortingFixed": [], 10321 10322 10323 /** 10324 * DataTables can be instructed to load data to display in the table from a 10325 * Ajax source. This option defines how that Ajax call is made and where to. 10326 * 10327 * The `ajax` property has three different modes of operation, depending on 10328 * how it is defined. These are: 10329 * 10330 * * `string` - Set the URL from where the data should be loaded from. 10331 * * `object` - Define properties for `jQuery.ajax`. 10332 * * `function` - Custom data get function 10333 * 10334 * `string` 10335 * -------- 10336 * 10337 * As a string, the `ajax` property simply defines the URL from which 10338 * DataTables will load data. 10339 * 10340 * `object` 10341 * -------- 10342 * 10343 * As an object, the parameters in the object are passed to 10344 * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control 10345 * of the Ajax request. DataTables has a number of default parameters which 10346 * you can override using this option. Please refer to the jQuery 10347 * documentation for a full description of the options available, although 10348 * the following parameters provide additional options in DataTables or 10349 * require special consideration: 10350 * 10351 * * `data` - As with jQuery, `data` can be provided as an object, but it 10352 * can also be used as a function to manipulate the data DataTables sends 10353 * to the server. The function takes a single parameter, an object of 10354 * parameters with the values that DataTables has readied for sending. An 10355 * object may be returned which will be merged into the DataTables 10356 * defaults, or you can add the items to the object that was passed in and 10357 * not return anything from the function. This supersedes `fnServerParams` 10358 * from DataTables 1.9-. 10359 * 10360 * * `dataSrc` - By default DataTables will look for the property `data` (or 10361 * `aaData` for compatibility with DataTables 1.9-) when obtaining data 10362 * from an Ajax source or for server-side processing - this parameter 10363 * allows that property to be changed. You can use Javascript dotted 10364 * object notation to get a data source for multiple levels of nesting, or 10365 * it my be used as a function. As a function it takes a single parameter, 10366 * the JSON returned from the server, which can be manipulated as 10367 * required, with the returned value being that used by DataTables as the 10368 * data source for the table. This supersedes `sAjaxDataProp` from 10369 * DataTables 1.9-. 10370 * 10371 * * `success` - Should not be overridden it is used internally in 10372 * DataTables. To manipulate / transform the data returned by the server 10373 * use `ajax.dataSrc`, or use `ajax` as a function (see below). 10374 * 10375 * `function` 10376 * ---------- 10377 * 10378 * As a function, making the Ajax call is left up to yourself allowing 10379 * complete control of the Ajax request. Indeed, if desired, a method other 10380 * than Ajax could be used to obtain the required data, such as Web storage 10381 * or an AIR database. 10382 * 10383 * The function is given four parameters and no return is required. The 10384 * parameters are: 10385 * 10386 * 1. _object_ - Data to send to the server 10387 * 2. _function_ - Callback function that must be executed when the required 10388 * data has been obtained. That data should be passed into the callback 10389 * as the only parameter 10390 * 3. _object_ - DataTables settings object for the table 10391 * 10392 * Note that this supersedes `fnServerData` from DataTables 1.9-. 10393 * 10394 * @type string|object|function 10395 * @default null 10396 * 10397 * @dtopt Option 10398 * @name DataTable.defaults.ajax
10399 * @since 1.10.0 10400 * 10401 * @example 10402 * // Get JSON data from a file via Ajax. 10403 * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). 10404 * $('#example').dataTable( { 10405 * "ajax": "data.json" 10406 * } ); 10407 * 10408 * @example 10409 * // Get JSON data from a file via Ajax, using `dataSrc` to change 10410 * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) 10411 * $('#example').dataTable( { 10412 * "ajax": { 10413 * "url": "data.json", 10414 * "dataSrc": "tableData" 10415 * } 10416 * } ); 10417 * 10418 * @example 10419 * // Get JSON data from a file via Ajax, using `dataSrc` to read data 10420 * // from a plain array rather than an array in an object 10421 * $('#example').dataTable( { 10422 * "ajax": { 10423 * "url": "data.json", 10424 * "dataSrc": "" 10425 * } 10426 * } ); 10427 * 10428 * @example 10429 * // Manipulate the data returned from the server - add a link to data 10430 * // (note this can, should, be done using `render` for the column - this 10431 * // is just a simple example of how the data can be manipulated). 10432 * $('#example').dataTable( { 10433 * "ajax": { 10434 * "url": "data.json", 10435 * "dataSrc": function ( json ) { 10436 * for ( var i=0, ien=json.length ; i<ien ; i++ ) { 10437 * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>'; 10438 * } 10439 * return json; 10440 * } 10441 * } 10442 * } ); 10443 * 10444 * @example 10445 * // Add data to the request 10446 * $('#example').dataTable( { 10447 * "ajax": { 10448 * "url": "data.json", 10449 * "data": function ( d ) { 10450 * return { 10451 * "extra_search": $('#extra').val() 10452 * }; 10453 * } 10454 * } 10455 * } ); 10456 * 10457 * @example 10458 * // Send request as POST 10459 * $('#example').dataTable( { 10460 * "ajax": { 10461 * "url": "data.json", 10462 * "type": "POST" 10463 * } 10464 * } ); 10465 * 10466 * @example 10467 * // Get the data from localStorage (could interface with a form for 10468 * // adding, editing and removing rows). 10469 * $('#example').dataTable( { 10470 * "ajax": function (data, callback, settings) { 10471 * callback( 10472 * JSON.parse( localStorage.getItem('dataTablesData') ) 10473 * ); 10474 * } 10475 * } ); 10476 */ 10477 "ajax": null, 10478 10479 10480 /** 10481 * This parameter allows you to readily specify the entries in the length drop 10482 * down menu that DataTables shows when pagination is enabled. It can be 10483 * either a 1D array of options which will be used for both the displayed 10484 * option and the value, or a 2D array which will use the array in the first 10485 * position as the value, and the array in the second position as the 10486 * displayed options (useful for language strings such as 'All'). 10487 * 10488 * Note that the `pageLength` property will be automatically set to the 10489 * first value given in this array, unless `pageLength` is also provided. 10490 * @type array 10491 * @default [ 10, 25, 50, 100 ] 10492 * 10493 * @dtopt Option 10494 * @name DataTable.defaults.lengthMenu 10495 * 10496 * @example 10497 * $(document).ready( function() { 10498 * $('#example').dataTable( { 10499 * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] 10500 * } ); 10501 * } ); 10502 */ 10503 "aLengthMenu": [ 10, 25, 50, 100 ], 10504 10505 10506 /** 10507 * The `columns` option in the initialisation parameter allows you to define 10508 * details about the way individual columns behave. For a full list of 10509 * column options that can be set, please see 10510 * {@link DataTable.defaults.column}. Note that if you use `columns` to 10511 * define your columns, you must have an entry in the array for every single 10512 * column that you have in your table (these can be null if you don't which 10513 * to specify any options). 10514 * @member 10515 * 10516 * @name DataTable.defaults.column 10517 */ 10518 "aoColumns": null, 10519 10520 /** 10521 * Very similar to `columns`, `columnDefs` allows you to target a specific 10522 * column, multiple columns, or all columns, using the `targets` property of 10523 * each object in the array. This allows great flexibility when creating 10524 * tables, as the `columnDefs` arrays can be of any length, targeting the 10525 * columns you specifically want. `columnDefs` may use any of the column 10526 * options available: {@link DataTable.defaults.column}, but it _must_ 10527 * have `targets` defined in each object in the array. Values in the `targets` 10528 * array may be: 10529 * <ul> 10530 * <li>
10530a string - class name will be matched on the TH for the column</li> 10531 * <li>0 or a positive integer - column index counting from the left</li> 10532 * <li>a negative integer - column index counting from the right</li> 10533 * <li>the string "_all" - all columns (i.e. assign a default)</li> 10534 * </ul> 10535 * @member 10536 * 10537 * @name DataTable.defaults.columnDefs 10538 */ 10539 "aoColumnDefs": null, 10540 10541 10542 /** 10543 * Basically the same as `search`, this parameter defines the individual column 10544 * filtering state at initialisation time. The array must be of the same size 10545 * as the number of columns, and each element be an object with the parameters 10546 * `search` and `escapeRegex` (the latter is optional). 'null' is also 10547 * accepted and the default will be used. 10548 * @type array 10549 * @default [] 10550 * 10551 * @dtopt Option 10552 * @name DataTable.defaults.searchCols 10553 * 10554 * @example 10555 * $(document).ready( function() { 10556 * $('#example').dataTable( { 10557 * "searchCols": [ 10558 * null, 10559 * { "search": "My filter" }, 10560 * null, 10561 * { "search": "^[0-9]", "escapeRegex": false } 10562 * ] 10563 * } ); 10564 * } ) 10565 */ 10566 "aoSearchCols": [], 10567 10568 10569 /** 10570 * An array of CSS classes that should be applied to displayed rows. This 10571 * array may be of any length, and DataTables will apply each class 10572 * sequentially, looping when required. 10573 * @type array 10574 * @default null <i>
10574Will take the values determined by the `oClasses.stripe*` 10575 * options</i> 10576 * 10577 * @dtopt Option 10578 * @name DataTable.defaults.stripeClasses 10579 * 10580 * @example 10581 * $(document).ready( function() { 10582 * $('#example').dataTable( { 10583 * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] 10584 * } ); 10585 * } ) 10586 */ 10587 "asStripeClasses": null, 10588 10589 10590 /** 10591 * Enable or disable automatic column width calculation. This can be disabled 10592 * as an optimisation (it takes some time to calculate the widths) if the 10593 * tables widths are passed in using `columns`. 10594 * @type boolean 10595 * @default true 10596 * 10597 * @dtopt Features 10598 * @name DataTable.defaults.autoWidth 10599 * 10600 * @example 10601 * $(document).ready( function () { 10602 * $('#example').dataTable( { 10603 * "autoWidth": false 10604 * } ); 10605 * } ); 10606 */ 10607 "bAutoWidth": true, 10608 10609 10610 /** 10611 * Deferred rendering can provide DataTables with a huge speed boost when you 10612 * are using an Ajax or JS data source for the table. This option, when set to 10613 * true, will cause DataTables to defer the creation of the table elements for 10614 * each row until they are needed for a draw - saving a significant amount of 10615 * time. 10616 * @type boolean 10617 * @default false 10618 * 10619 * @dtopt Features 10620 * @name DataTable.defaults.deferRender 10621 * 10622 * @example 10623 * $(document).ready( function() { 10624 * $('#example').dataTable( { 10625 * "ajax": "sources/arrays.txt", 10626 * "deferRender": true 10627 * } ); 10628 * } ); 10629 */ 10630 "bDeferRender": false, 10631 10632 10633 /** 10634 * Replace a DataTable which matches the given selector and replace it with 10635 * one which has the properties of the new initialisation object passed. If no 10636 * table matches the selector, then the new DataTable will be constructed as 10637 * per normal. 10638 * @type boolean 10639 * @default false 10640 * 10641 * @dtopt Options 10642 * @name DataTable.defaults.destroy 10643 * 10644 * @example 10645 * $(document).ready( function() { 10646 * $('#example').dataTable( { 10647 * "srollY": "200px", 10648 * "paginate": false 10649 * } ); 10650 * 10651 * // Some time later.... 10652 * $('#example').dataTable( { 10653 * "filter": false, 10654 * "destroy": true 10655 * } ); 10656 * } ); 10657 */ 10658 "bDestroy": false, 10659 10660 10661 /** 10662 * Enable or disable filtering of data. Filtering in DataTables is "smart" in 10663 * that it allows the end user to input multiple words (space separated) and 10664 * will match a row containing those words, even if not in the order that was 10665 * specified (this allow matching across multiple columns). Note that if you 10666 * wish to use filtering in DataTables this must remain 'true' - to remove the 10667 * default filtering input box and retain filtering abilities, please use 10668 * {@link DataTable.defaults.dom}. 10669 * @type boolean 10670 * @default true 10671 * 10672 * @dtopt Features 10673 * @name DataTable.defaults.searching 10674 * 10675 * @example 10676 * $(document).ready( function () { 10677 * $('#example').dataTable( { 10678 * "searching": false 10679 * } ); 10680 * } ); 10681 */ 10682 "bFilter": true, 10683 10684 10685 /** 10686 * Enable or disable the table information display. This shows information 10687 * about the data that is currently visible on the page, including information 10688 * about filtered data if that action is being performed. 10689 * @type boolean 10690 * @default true 10691 * 10692 * @dtopt Features 10693 * @name DataTable.defaults.info 10694 * 10695 * @example 10696 * $(document).ready( function () { 10697 * $('#example').dataTable( { 10698 * "info": false 10699 * } ); 10700 * } ); 10701 */ 10702 "bInfo": true, 10703 10704 10705 /** 10706 * Allows the end user to select the size of a formatted page from a select 10707 * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). 10708 * @type boolean 10709 * @default true 10710 * 10711 * @dtopt Features 10712 * @name DataTable.defaults.lengthChange 10713 * 10714 * @example 10715 * $(document).ready( function () { 10716 * $('#example').dataTable( { 10717 * "lengthChange": false 10718 * } ); 10719 * } ); 10720 */ 10721 "bLengthChange": true, 10722 10723 10724 /** 10725 * Enable or disable pagination. 10726 * @type boolean 10727 * @default true 10728 * 10729 * @dtopt Features 10730 * @name DataTable.defaults.paging 10731 * 10732 * @example 10733 * $(document).ready( function () { 10734 * $('#example').dataTable( { 10735 * "paging": false 10736 * } ); 10737 * } ); 10738 */ 10739 "bPaginate": true, 10740 10741 10742 /** 10743 * Enable or disable the display of a 'processing' indicator when the table is 10744 * being processed (e.g. a sort). This is particularly useful for tables with 10745 * large amounts of data where it can take a noticeable amount of time to sort 10746 * the entries. 10747 * @type boolean 10748 * @default false 10749 * 10750 * @dtopt Features 10751 * @name DataTable.defaults.processing 10752 * 10753 * @example 10754 * $(document).ready( function () { 10755 * $('#example').dataTable( { 10756 * "processing": true 10757 * } ); 10758 * } ); 10759 */ 10760 "bProcessing": false, 10761 10762 10763 /** 10764 * Retrieve the DataTables object for the given selector. Note that if the 10765 * table has already been initialised, this parameter will cause DataTables 10766 * to simply return the object that has already been set up - it will not take 10767 * account of any changes you might have made to the initialisation object 10768 * passed to DataTables (setting this parameter to true is an acknowledgement 10769 * that you understand this). `destroy` can be used to reinitialise a table if 10770 * you need. 10771 * @type boolean 10772 * @default false 10773 * 10774 * @dtopt Options 10775 * @name DataTable.defaults.retrieve 10776 * 10777 * @example 10778 * $(document).ready( function() { 10779 * initTable(); 10780 * tableActions(); 10781 * } ); 10782 * 10783 * function initTable () 10784 * { 10785 * return $('#example').dataTable( { 10786 * "scrollY": "200px", 10787 * "paginate": false, 10788 * "retrieve": true 10789 * } ); 10790 * } 10791 * 10792 * function tableActions () 10793 * { 10794 * var table = initTable(); 10795 * // perform API operations with oTable 10796 * } 10797 */ 10798 "bRetrieve": false, 10799 10800 10801 /** 10802 * When vertical (y) scrolling is enabled, DataTables will force the height of
10803 * the table's viewport to the given height at all times (useful for layout). 10804 * However, this can look odd when filtering data down to a small data set, 10805 * and the footer is left "floating" further down. This parameter (when 10806 * enabled) will cause DataTables to collapse the table's viewport down when 10807 * the result set will fit within the given Y height. 10808 * @type boolean 10809 * @default false 10810 * 10811 * @dtopt Options 10812 * @name DataTable.defaults.scrollCollapse 10813 * 10814 * @example 10815 * $(document).ready( function() { 10816 * $('#example').dataTable( { 10817 * "scrollY": "200", 10818 * "scrollCollapse": true 10819 * } ); 10820 * } ); 10821 */ 10822 "bScrollCollapse": false, 10823 10824 10825 /** 10826 * Configure DataTables to use server-side processing. Note that the 10827 * `ajax` parameter must also be given in order to give DataTables a 10828 * source to obtain the required data for each draw. 10829 * @type boolean 10830 * @default false 10831 * 10832 * @dtopt Features 10833 * @dtopt Server-side 10834 * @name DataTable.defaults.serverSide 10835 * 10836 * @example 10837 * $(document).ready( function () { 10838 * $('#example').dataTable( { 10839 * "serverSide": true, 10840 * "ajax": "xhr.php" 10841 * } ); 10842 * } ); 10843 */ 10844 "bServerSide": false, 10845 10846 10847 /** 10848 * Enable or disable sorting of columns. Sorting of individual columns can be 10849 * disabled by the `sortable` option for each column. 10850 * @type boolean 10851 * @default true 10852 * 10853 * @dtopt Features 10854 * @name DataTable.defaults.ordering 10855 * 10856 * @example 10857 * $(document).ready( function () { 10858 * $('#example').dataTable( { 10859 * "ordering": false 10860 * } ); 10861 * } ); 10862 */ 10863 "bSort": true, 10864 10865 10866 /** 10867 * Enable or display DataTables' ability to sort multiple columns at the 10868 * same time (activated by shift-click by the user). 10869 * @type boolean 10870 * @default true 10871 * 10872 * @dtopt Options 10873 * @name DataTable.defaults.orderMulti 10874 * 10875 * @example 10876 * // Disable multiple column sorting ability 10877 * $(document).ready( function () { 10878 * $('#example').dataTable( { 10879 * "orderMulti": false 10880 * } ); 10881 * } ); 10882 */ 10883 "bSortMulti": true, 10884 10885 10886 /** 10887 * Allows control over whether DataTables should use the top (true) unique 10888 * cell that is found for a single column, or the bottom (false - default). 10889 * This is useful when using complex headers. 10890 * @type boolean 10891 * @default false 10892 * 10893 * @dtopt Options 10894 * @name DataTable.defaults.orderCellsTop 10895 * 10896 * @example 10897 * $(document).ready( function() { 10898 * $('#example').dataTable( { 10899 * "orderCellsTop": true 10900 * } ); 10901 * } ); 10902 */ 10903 "bSortCellsTop": false, 10904 10905 10906 /** 10907 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and 10908 * `sorting\_3` to the columns which are currently being sorted on. This is 10909 * presented as a feature switch as it can increase processing time (while 10910 * classes are removed and added) so for large data sets you might want to 10911 * turn this off. 10912 * @type boolean 10913 * @default true 10914 * 10915 * @dtopt Features 10916 * @name DataTable.defaults.orderClasses 10917 * 10918 * @example 10919 * $(document).ready( function () { 10920 * $('#example').dataTable( { 10921 * "orderClasses": false 10922 * } ); 10923 * } ); 10924 */ 10925 "bSortClasses": true, 10926 10927 10928 /** 10929 * Enable or disable state saving. When enabled HTML5 `localStorage` will be 10930 * used to save table display information such as pagination information, 10931 * display length, filtering and sorting. As such when the end user reloads 10932 * the page the display display will match what thy had previously set up. 10933 * 10934 * Due to the use of `localStorage` the default state saving is not supported 10935 * in IE6 or 7. If state saving is required in those browsers, use 10936 * `stateSaveCallback` to provide a storage solution such as cookies. 10937 * @type boolean 10938 * @default false 10939 * 10940 * @dtopt Features 10941 * @name DataTable.defaults.stateSave 10942 * 10943 * @example 10944 * $(document).ready( function () { 10945 * $('#example').dataTable( { 10946 * "stateSave": true 10947 * } ); 10948 * } ); 10949 */ 10950 "bStateSave": false, 10951 10952 10953 /** 10954 * This function is called when a TR element is created (and all TD child 10955 * elements have been inserted), or registered if using a DOM source, allowing 10956 * manipulation of the TR element (adding classes etc). 10957 * @type function 10958 * @param {node} row "TR" element for the current row 10959 * @param {array} data Raw data array for this row 10960 * @param {int} dataIndex The index of this row in the internal aoData array 10961 * 10962 * @dtopt Callbacks 10963 * @name DataTable.defaults.createdRow 10964 * 10965 * @example 10966 * $(document).ready( function() { 10967 * $('#example').dataTable( { 10968 * "createdRow": function( row, data, dataIndex ) { 10969 * // Bold the grade for all 'A' grade browsers 10970 * if ( data[4] == "A" ) 10971 * { 10972 * $('td:eq(4)', row).html( '<b>A</b>' ); 10973 * } 10974 * } 10975 * } ); 10976 * } ); 10977 */ 10978 "fnCreatedRow": null, 10979 10980 10981 /** 10982 * This function is called on every 'draw' event, and allows you to 10983 * dynamically modify any aspect you want about the created DOM. 10984 * @type function 10985 * @param {object} settings DataTables settings object 10986 * 10987 * @dtopt Callbacks 10988 * @name DataTable.defaults.drawCallback 10989 * 10990 * @example 10991 * $(document).ready( function() { 10992 * $('#example').dataTable( { 10993 * "drawCallback": function( settings ) { 10994 * alert( 'DataTables has redrawn the table' ); 10995 * } 10996 * } ); 10997 * } ); 10998 */ 10999 "fnDrawCallback": null, 11000 11001 11002 /** 11003 * Identical to fnHeaderCallback() but for the table footer this function 11004 * allows you to modify the table footer on every 'draw' event. 11005 * @type function 11006 * @param {node} foot "TR" element for the footer 11007 * @param {array} data Full table data (as derived from the original HTML) 11008 * @param {int} start Index for the current display starting point in the 11009 * display array 11010 * @param {int} end Index for the current display ending point in the 11011 * display array 11012 * @param {array int} display Index array to translate the visual position 11013 * to the full data array 11014 * 11015 * @dtopt Callbacks 11016 * @name DataTable.defaults.footerCallback 11017 * 11018 * @example 11019 * $(document).ready( function() { 11020 * $('#example').dataTable( { 11021 * "footerCallback": function( tfoot, data, start, end, display ) { 11022 * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; 11023 * } 11024 * } ); 11025 * } ) 11026 */ 11027 "fnFooterCallback": null, 11028 11029 11030 /** 11031 * When rendering large numbers in the information element for the table 11032 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers 11033 * to have a comma separator for the 'thousands' units (e.g. 1 million is 11034 * rendered as "1,000,000") to help readability for the end user. This 11035 * function will override the default method DataTables uses. 11036 * @type function 11037 * @member 11038 * @param {int} toFormat number to be formatted 11039 * @returns {string} formatted string for DataTables to show the number 11040 * 11041 * @dtopt Callbacks 11042 * @name DataTable.defaults.formatNumber 11043 * 11044 * @example 11045 * // Format a number using a single quote for the separator (note that
11046 * // this can also be done with the language.thousands option) 11047 * $(document).ready( function() { 11048 * $('#example').dataTable( { 11049 * "formatNumber": function ( toFormat ) { 11050 * return toFormat.toString().replace( 11051 * /\B(?=(\d{3})+(?!\d))/g, "'" 11052 * ); 11053 * }; 11054 * } ); 11055 * } ); 11056 */ 11057 "fnFormatNumber": function ( toFormat ) { 11058 return toFormat.toString().replace( 11059 /\B(?=(\d{3})+(?!\d))/g, 11060 this.oLanguage.sThousands 11061 ); 11062 }, 11063 11064 11065 /** 11066 * This function is called on every 'draw' event, and allows you to 11067 * dynamically modify the header row. This can be used to calculate and 11068 * display useful information about the table. 11069 * @type function 11070 * @param {node} head "TR" element for the header 11071 * @param {array} data Full table data (as derived from the original HTML) 11072 * @param {int} start Index for the current display starting point in the 11073 * display array 11074 * @param {int} end Index for the current display ending point in the 11075 * display array 11076 * @param {array int} display Index array to translate the visual position 11077 * to the full data array 11078 * 11079 * @dtopt Callbacks 11080 * @name DataTable.defaults.headerCallback 11081 * 11082 * @example 11083 * $(document).ready( function() { 11084 * $('#example').dataTable( { 11085 * "fheaderCallback": function( head, data, start, end, display ) { 11086 * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; 11087 * } 11088 * } ); 11089 * } ) 11090 */ 11091 "fnHeaderCallback": null, 11092 11093 11094 /** 11095 * The information element can be used to convey information about the current 11096 * state of the table. Although the internationalisation options presented by 11097 * DataTables are quite capable of dealing with most customisations, there may 11098 * be times where you wish to customise the string further. This callback 11099 * allows you to do exactly that. 11100 * @type function 11101 * @param {object} oSettings DataTables settings object 11102 * @param {int} start Starting position in data for the draw 11103 * @param {int} end End position in data for the draw 11104 * @param {int} max Total number of rows in the table (regardless of 11105 * filtering) 11106 * @param {int} total Total number of rows in the data set, after filtering 11107 * @param {string} pre The string that DataTables has formatted using it's 11108 * own rules 11109 * @returns {string} The string to be displayed in the information element. 11110 * 11111 * @dtopt Callbacks 11112 * @name DataTable.defaults.infoCallback 11113 * 11114 * @example 11115 * $('#example').dataTable( { 11116 * "infoCallback": function( settings, start, end, max, total, pre ) { 11117 * return start +" to "+ end; 11118 * } 11119 * } ); 11120 */ 11121 "fnInfoCallback": null, 11122 11123 11124 /** 11125 * Called when the table has been initialised. Normally DataTables will 11126 * initialise sequentially and there will be no need for this function, 11127 * however, this does not hold true when using external language information 11128 * since that is obtained using an async XHR call. 11129 * @type function 11130 * @param {object} settings DataTables settings object 11131 * @param {object} json The JSON object request from the server - only 11132 * present if client-side Ajax sourced data is used 11133 * 11134 * @dtopt Callbacks 11135 * @name DataTable.defaults.initComplete 11136 * 11137 * @example 11138 * $(document).ready( function() { 11139 * $('#example').dataTable( { 11140 * "initComplete": function(settings, json) { 11141 * alert( 'DataTables has finished its initialisation.' ); 11142 * } 11143 * } ); 11144 * } ) 11145 */ 11146 "fnInitComplete": null, 11147 11148 11149 /** 11150 * Called at the very start of each table draw and can be used to cancel the 11151 * draw by returning false, any other return (including undefined) results in 11152 * the full draw occurring). 11153 * @type function 11154 * @param {object} settings DataTables settings object 11155 * @returns {boolean} False will cancel the draw, anything else (including no 11156 * return) will allow it to complete. 11157 * 11158 * @dtopt Callbacks 11159 * @name DataTable.defaults.preDrawCallback 11160 * 11161 * @example 11162 * $(document).ready( function() { 11163 * $('#example').dataTable( { 11164 * "preDrawCallback": function( settings ) { 11165 * if ( $('#test').val() == 1 ) { 11166 * return false; 11167 * } 11168 * } 11169 * } ); 11170 * } ); 11171 */ 11172 "fnPreDrawCallback": null, 11173 11174 11175 /** 11176 * This function allows you to 'post process' each row after it have been 11177 * generated for each table draw, but before it is rendered on screen. This 11178 * function might be used for setting the row class name etc. 11179 * @type function 11180 * @param {node} row "TR" element for the current row 11181 * @param {array} data Raw data array for this row 11182 * @param {int} displayIndex The display index for the current table draw 11183 * @param {int} displayIndexFull The index of the data in the full list of 11184 * rows (after filtering) 11185 * 11186 * @dtopt Callbacks 11187 * @name DataTable.defaults.rowCallback 11188 * 11189 * @example 11190 * $(document).ready( function() { 11191 * $('#example').dataTable( { 11192 * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { 11193 * // Bold the grade for all 'A' grade browsers 11194 * if ( data[4] == "A" ) { 11195 * $('td:eq(4)', row).html( '<b>A</b>' ); 11196 * } 11197 * } 11198 * } ); 11199 * } ); 11200 */ 11201 "fnRowCallback": null, 11202 11203 11204 /** 11205 * __Deprecated__ The functionality provided by this parameter has now been 11206 * superseded by that provided through `ajax`, which should be used instead. 11207 * 11208 * This parameter allows you to override the default function which obtains 11209 * the data from the server so something more suitable for your application. 11210 * For example you could use POST data, or pull information from a Gears or 11211 * AIR database. 11212 * @type function 11213 * @member 11214 * @param {string} source HTTP source to obtain the data from (`ajax`) 11215 * @param {array}
11215 data A key/value pair object containing the data to send 11216 * to the server 11217 * @param {function} callback to be called on completion of the data get 11218 * process that will draw the data on the page. 11219 * @param {object} settings DataTables settings object 11220 * 11221 * @dtopt Callbacks 11222 * @dtopt Server-side 11223 * @name DataTable.defaults.serverData 11224 * 11225 * @deprecated 1.10. Please use `ajax` for this functionality now. 11226 */ 11227 "fnServerData": null, 11228 11229 11230 /** 11231 * __Deprecated__ The functionality provided by this parameter has now been 11232 * superseded by that provided through `ajax`, which should be used instead. 11233 * 11234 * It is often useful to send extra data to the server when making an Ajax 11235 * request - for example custom filtering information, and this callback 11236 * function makes it trivial to send extra information to the server. The 11237 * passed in parameter is the data set that has been constructed by 11238 * DataTables, and you can add to this or modify it as you require. 11239 * @type function 11240 * @param {array} data Data array (array of objects which are name/value 11241 * pairs) that has been constructed by DataTables and will be sent to the 11242 * server. In the case of Ajax sourced data with server-side processing 11243 * this will be an empty array, for server-side processing there will be a 11244 * significant number of parameters! 11245 * @returns {undefined} Ensure that you modify the data array passed in, 11246 * as this is passed by reference. 11247 * 11248 * @dtopt Callbacks 11249 * @dtopt Server-side 11250 * @name DataTable.defaults.serverParams 11251 * 11252 * @deprecated 1.10. Please use `ajax` for this functionality now. 11253 */ 11254 "fnServerParams": null, 11255 11256 11257 /** 11258 * Load the table state. With this function you can define from where, and how, the 11259 * state of a table is loaded. By default DataTables will load from `localStorage` 11260 * but you might wish to use a server-side database or cookies. 11261 * @type function 11262 * @member 11263 * @param {object} settings DataTables settings object 11264 * @param {object} callback Callback that can be executed when done. It 11265 * should be passed the loaded state object. 11266 * @return {object} The DataTables state object to be loaded 11267 * 11268 * @dtopt Callbacks 11269 * @name DataTable.defaults.stateLoadCallback 11270 * 11271 * @example 11272 * $(document).ready( function() { 11273 * $('#example').dataTable( { 11274 * "stateSave": true, 11275 * "stateLoadCallback": function (settings, callback) { 11276 * $.ajax( { 11277 * "url": "/state_load", 11278 * "dataType": "json", 11279 * "success": function (json) { 11280 * callback( json ); 11281 * } 11282 * } ); 11283 * } 11284 * } ); 11285 * } ); 11286 */ 11287 "fnStateLoadCallback": function ( settings ) { 11288 try { 11289 return JSON.parse( 11290 (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( 11291 'DataTables_'+settings.sInstance+'_'+location.pathname 11292 ) 11293 ); 11294 } catch (e) { 11295 return {}; 11296 } 11297 }, 11298 11299 11300 /** 11301 * Callback which allows modification of the saved state prior to loading that state. 11302 * This callback is called when the table is loading state from the stored data, but 11303 * prior to the settings object being modified by the saved state. Note that for 11304 * plug-in authors, you should use the `stateLoadParams` event to load parameters for 11305 * a plug-in. 11306 * @type function 11307 * @param {object} settings DataTables settings object 11308 * @param {object} data The state object that is to be loaded 11309 * 11310 * @dtopt Callbacks 11311 * @name DataTable.defaults.stateLoadParams 11312 * 11313 * @example 11314 * // Remove a saved filter, so filtering is never loaded 11315 * $(document).ready( function() { 11316 * $('#example').dataTable( { 11317 * "stateSave": true, 11318 * "stateLoadParams": function (settings, data) { 11319 * data.oSearch.sSearch = ""; 11320 * } 11321 * } ); 11322 * } ); 11323 * 11324 * @example 11325 * // Disallow state loading by returning false 11326 * $(document).ready( function() { 11327 * $('#example').dataTable( { 11328 * "stateSave": true, 11329 * "stateLoadParams": function (settings, data) { 11330 * return false; 11331 * } 11332 * } ); 11333 * } ); 11334 */ 11335 "fnStateLoadParams": null, 11336 11337 11338 /** 11339 * Callback that is called when the state has been loaded from the state saving method 11340 * and the DataTables settings object has been modified as a result of the loaded state. 11341 * @type function 11342 * @param {object} settings DataTables settings object 11343 * @param {object} data The state object that was loaded 11344 * 11345 * @dtopt Callbacks 11346 * @name DataTable.defaults.stateLoaded 11347 * 11348 * @example 11349 * // Show an alert with the filtering value that was saved 11350 * $(document).ready( function() { 11351 * $('#example').dataTable( { 11352 * "stateSave": true, 11353 * "stateLoaded": function (settings, data) { 11354 * alert( 'Saved filter was: '+data.oSearch.sSearch ); 11355 * } 11356 * } ); 11357 * } ); 11358 */ 11359 "fnStateLoaded": null, 11360 11361 11362 /** 11363 * Save the table state. This function allows you to define where and how the state 11364 * information for the table is stored By default DataTables will use `localStorage` 11365 * but you might wish to use a server-side database or cookies. 11366 * @type function 11367 * @member 11368 * @param {object} settings DataTables settings object 11369 * @param {object} data The state object to be saved 11370 * 11371 * @dtopt Callbacks 11372 * @name DataTable.defaults.stateSaveCallback 11373 * 11374 * @example 11375 * $(document).ready( function() { 11376 * $('#example').dataTable( { 11377 * "stateSave": true, 11378 * "stateSaveCallback": function (settings, data) { 11379 * // Send an Ajax request to the server with the state object 11380 * $.ajax( { 11381 * "url": "/state_save", 11382 * "data": data, 11383 * "dataType": "json", 11384 * "method": "POST" 11385 * "success": function () {} 11386 * } ); 11387 * } 11388 * } ); 11389 * } ); 11390 */ 11391 "fnStateSaveCallback": function ( settings, data ) { 11392 try {
11393 (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( 11394 'DataTables_'+settings.sInstance+'_'+location.pathname, 11395 JSON.stringify( data ) 11396 ); 11397 } catch (e) {} 11398 }, 11399 11400 11401 /** 11402 * Callback which allows modification of the state to be saved. Called when the table 11403 * has changed state a new state save is required. This method allows modification of 11404 * the state saving object prior to actually doing the save, including addition or 11405 * other state properties or modification. Note that for plug-in authors, you should 11406 * use the `stateSaveParams` event to save parameters for a plug-in. 11407 * @type function 11408 * @param {object} settings DataTables settings object 11409 * @param {object} data The state object to be saved 11410 * 11411 * @dtopt Callbacks 11412 * @name DataTable.defaults.stateSaveParams 11413 * 11414 * @example 11415 * // Remove a saved filter, so filtering is never saved 11416 * $(document).ready( function() { 11417 * $('#example').dataTable( { 11418 * "stateSave": true, 11419 * "stateSaveParams": function (settings, data) { 11420 * data.oSearch.sSearch = ""; 11421 * } 11422 * } ); 11423 * } ); 11424 */ 11425 "fnStateSaveParams": null, 11426 11427 11428 /** 11429 * Duration for which the saved state information is considered valid. After this period 11430 * has elapsed the state will be returned to the default. 11431 * Value is given in seconds. 11432 * @type int 11433 * @default 7200 <i>(2 hours)</i> 11434 * 11435 * @dtopt Options 11436 * @name DataTable.defaults.stateDuration 11437 * 11438 * @example 11439 * $(document).ready( function() { 11440 * $('#example').dataTable( { 11441 * "stateDuration": 60*60*24; // 1 day 11442 * } ); 11443 * } ) 11444 */ 11445 "iStateDuration": 7200, 11446 11447 11448 /** 11449 * When enabled DataTables will not make a request to the server for the first 11450 * page draw - rather it will use the data already on the page (no sorting etc 11451 * will be applied to it), thus saving on an XHR at load time. `deferLoading` 11452 * is used to indicate that deferred loading is required, but it is also used 11453 * to tell DataTables how many records there are in the full table (allowing 11454 * the information element and pagination to be displayed correctly). In the case 11455 * where a filtering is applied to the table on initial load, this can be 11456 * indicated by giving the parameter as an array, where the first element is 11457 * the number of records available after filtering and the second element is the 11458 * number of records without filtering (allowing the table information element 11459 * to be shown correctly). 11460 * @type int | array 11461 * @default null 11462 * 11463 * @dtopt Options 11464 * @name DataTable.defaults.deferLoading 11465 * 11466 * @example 11467 * // 57 records available in the table, no filtering applied 11468 * $(document).ready( function() { 11469 * $('#example').dataTable( { 11470 * "serverSide": true, 11471 * "ajax": "scripts/server_processing.php", 11472 * "deferLoading": 57 11473 * } ); 11474 * } ); 11475 * 11476 * @example 11477 * // 57 records after filtering, 100 without filtering (an initial filter applied) 11478 * $(document).ready( function() { 11479 * $('#example').dataTable( { 11480 * "serverSide": true, 11481 * "ajax": "scripts/server_processing.php", 11482 * "deferLoading": [ 57, 100 ], 11483 * "search": { 11484 * "search": "my_filter" 11485 * } 11486 * } ); 11487 * } ); 11488 */ 11489 "iDeferLoading": null, 11490 11491 11492 /** 11493 * Number of rows to display on a single page when using pagination. If 11494 * feature enabled (`lengthChange`) then the end user will be able to override 11495 * this to a custom setting using a pop-up menu. 11496 * @type int 11497 * @default 10 11498 * 11499 * @dtopt Options 11500 * @name DataTable.defaults.pageLength 11501 * 11502 * @example 11503 * $(document).ready( function() { 11504 * $('#example').dataTable( { 11505 * "pageLength": 50 11506 * } ); 11507 * } ) 11508 */ 11509 "iDisplayLength": 10, 11510 11511 11512 /** 11513 * Define the starting point for data display when using DataTables with 11514 * pagination. Note that this parameter is the number of records, rather than 11515 * the page number, so if you have 10 records per page and want to start on 11516 * the third page, it should be "20". 11517 * @type int 11518 * @default 0 11519 * 11520 * @dtopt Options 11521 * @name DataTable.defaults.displayStart
11522 * 11523 * @example 11524 * $(document).ready( function() { 11525 * $('#example').dataTable( { 11526 * "displayStart": 20 11527 * } ); 11528 * } ) 11529 */ 11530 "iDisplayStart": 0, 11531 11532 11533 /** 11534 * By default DataTables allows keyboard navigation of the table (sorting, paging, 11535 * and filtering) by adding a `tabindex` attribute to the required elements. This 11536 * allows you to tab through the controls and press the enter key to activate them. 11537 * The tabindex is default 0, meaning that the tab follows the flow of the document. 11538 * You can overrule this using this parameter if you wish. Use a value of -1 to 11539 * disable built-in keyboard navigation. 11540 * @type int 11541 * @default 0 11542 * 11543 * @dtopt Options 11544 * @name DataTable.defaults.tabIndex 11545 * 11546 * @example 11547 * $(document).ready( function() { 11548 * $('#example').dataTable( { 11549 * "tabIndex": 1 11550 * } ); 11551 * } ); 11552 */ 11553 "iTabIndex": 0, 11554 11555 11556 /** 11557 * Classes that DataTables assigns to the various components and features 11558 * that it adds to the HTML table. This allows classes to be configured 11559 * during initialisation in addition to through the static 11560 * {@link DataTable.ext.oStdClasses} object). 11561 * @namespace 11562 * @name DataTable.defaults.classes 11563 */ 11564 "oClasses": {}, 11565 11566 11567 /** 11568 * All strings that DataTables uses in the user interface that it creates 11569 * are defined in this object, allowing you to modified them individually or 11570 * completely replace them all as required. 11571 * @namespace 11572 * @name DataTable.defaults.language 11573 */ 11574 "oLanguage": { 11575 /** 11576 * Strings that are used for WAI-ARIA labels and controls only (these are not 11577 * actually visible on the page, but will be read by screenreaders, and thus 11578 * must be internationalised as well). 11579 * @namespace 11580 * @name DataTable.defaults.language.aria 11581 */ 11582 "oAria": { 11583 /** 11584 * ARIA label that is added to the table headers when the column may be 11585 * sorted ascending by activing the column (click or return when focused). 11586 * Note that the column header is prefixed to this string. 11587 * @type string 11588 * @default : activate to sort column ascending 11589 * 11590 * @dtopt Language 11591 * @name DataTable.defaults.language.aria.sortAscending 11592 * 11593 * @example 11594 * $(document).ready( function() { 11595 * $('#example').dataTable( { 11596 * "language": { 11597 * "aria": { 11598 * "sortAscending": " - click/return to sort ascending" 11599 * } 11600 * } 11601 * } ); 11602 * } ); 11603 */ 11604 "sSortAscending": ": activate to sort column ascending", 11605 11606 /** 11607 * ARIA label that is added to the table headers when the column may be 11608 * sorted descending by activing the column (click or return when focused). 11609 * Note that the column header is prefixed to this string. 11610 * @type string 11611 * @default : activate to sort column ascending 11612 * 11613 * @dtopt Language 11614 * @name DataTable.defaults.language.aria.sortDescending 11615 * 11616 * @example 11617 * $(document).ready( function() { 11618 * $('#example').dataTable( { 11619 * "language": { 11620 * "aria": { 11621 * "sortDescending": " - click/return to sort descending" 11622 * } 11623 * } 11624 * } ); 11625 * } ); 11626 */ 11627 "sSortDescending": ": activate to sort column descending" 11628 }, 11629 11630 /** 11631 * Pagination string used by DataTables for the built-in pagination 11632 * control types. 11633 * @namespace 11634 * @name DataTable.defaults.language.paginate 11635 */ 11636 "oPaginate": { 11637 /** 11638 * Text to use when using the 'full_numbers' type of pagination for the 11639 * button to take the user to the first page. 11640 * @type string 11641 * @default First 11642 * 11643 * @dtopt Language 11644 * @name DataTable.defaults.language.paginate.first 11645 * 11646 * @example 11647 * $(document).ready( function() { 11648 * $('#example').dataTable( { 11649 * "language": { 11650 * "paginate": { 11651 * "first": "First page" 11652 * } 11653 * } 11654 * } ); 11655 * } ); 11656 */ 11657 "sFirst": "First", 11658 11659 11660 /** 11661 * Text to use when using the 'full_numbers' type of pagination for the 11662 * button to take the user to the last page. 11663 * @type string 11664 * @default Last 11665 * 11666 * @dtopt Language 11667 * @name DataTable.defaults.language.paginate.last 11668 * 11669 * @example 11670 * $(document).ready( function() { 11671 * $('#example').dataTable( { 11672 * "language": { 11673 * "paginate": { 11674 * "last": "Last page" 11675 * } 11676 * } 11677 * } ); 11678 * } ); 11679 */ 11680 "sLast": "Last", 11681 11682 11683 /** 11684 * Text to use for the 'next' pagination button (to take the user to the 11685 * next page). 11686 * @type string 11687 * @default Next 11688 * 11689 * @dtopt Language 11690 * @name DataTable.defaults.language.paginate.next 11691 * 11692 * @example 11693 * $(document).ready( function() { 11694 * $('#example').dataTable( { 11695 * "language": { 11696 * "paginate": { 11697 * "next": "Next page" 11698 * } 11699 * } 11700 * } ); 11701 * } ); 11702 */ 11703 "sNext": "Next", 11704 11705 11706 /** 11707 * Text to use for the 'previous' pagination button (to take the user to 11708 * the previous page). 11709 * @type string 11710 * @default Previous 11711 * 11712 * @dtopt Language 11713 * @name DataTable.defaults.language.paginate.previous 11714 * 11715 * @example 11716 * $(document).ready( function() { 11717 * $('#example').dataTable( { 11718 * "language": { 11719 * "paginate": { 11720 * "previous": "Previous page" 11721 * } 11722 * } 11723 * } ); 11724 * } ); 11725 */ 11726 "sPrevious": "Previous" 11727 }, 11728 11729 /** 11730 * This string is shown in preference to `zeroRecords` when the table is 11731 * empty of data (regardless of filtering). Note that this is an optional 11732 * parameter - if it is not given, the value of `zeroRecords` will be used 11733 * instead (either the default or given value). 11734 * @type string 11735 * @default No data available in table 11736 * 11737 * @dtopt Language 11738 * @name DataTable.defaults.language.emptyTable 11739 * 11740 * @example 11741 * $(document).ready( function() { 11742 * $('#example').dataTable( { 11743 * "language": { 11744 * "emptyTable": "No data available in table" 11745 * } 11746 * } ); 11747 * } ); 11748 */ 11749 "sEmptyTable": "No data available in table", 11750 11751 11752 /** 11753 * This string gives information to the end user about the information 11754 * that is current on display on the page. The following tokens can be 11755 * used in the string and will be dynamically replaced as the table 11756 * display updates. This tokens can be placed anywhere in the string, or 11757 * removed as needed by the language requires: 11758 * 11759 * * `\_START\_` - Display index of the first record on the current page 11760 * * `\_END\_` - Display index of the last record on the current page 11761 * * `\_TOTAL\_` - Number of records in the table after filtering 11762 * * `\_MAX\_` - Number of records in the table without filtering 11763 * * `\_PAGE\_` - Current page number 11764 * * `\_PAGES\_` - Total number of pages of data in the table 11765 * 11766 * @type string 11767 * @default Showing _START_ to _END_ of _TOTAL_ entries 11768 * 11769 * @dtopt Language 11770 * @name DataTable.defaults.language.info 11771 * 11772 * @example 11773 * $(document).ready( function() { 11774 * $('#example').dataTable( { 11775 * "language": { 11776 * "info": "Showing page _PAGE_ of _PAGES_" 11777 * } 11778 * } ); 11779 * } ); 11780 */ 11781 "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", 11782 11783 11784 /**
11785 * Display information string for when the table is empty. Typically the 11786 * format of this string should match `info`. 11787 * @type string 11788 * @default Showing 0 to 0 of 0 entries 11789 * 11790 * @dtopt Language 11791 * @name DataTable.defaults.language.infoEmpty 11792 * 11793 * @example 11794 * $(document).ready( function() { 11795 * $('#example').dataTable( { 11796 * "language": { 11797 * "infoEmpty": "No entries to show" 11798 * } 11799 * } ); 11800 * } ); 11801 */ 11802 "sInfoEmpty": "Showing 0 to 0 of 0 entries", 11803 11804 11805 /** 11806 * When a user filters the information in a table, this string is appended 11807 * to the information (`info`) to give an idea of how strong the filtering 11808 * is. The variable _MAX_ is dynamically updated. 11809 * @type string 11810 * @default (filtered from _MAX_ total entries) 11811 * 11812 * @dtopt Language 11813 * @name DataTable.defaults.language.infoFiltered 11814 * 11815 * @example 11816 * $(document).ready( function() { 11817 * $('#example').dataTable( { 11818 * "language": { 11819 * "infoFiltered": " - filtering from _MAX_ records" 11820 * } 11821 * } ); 11822 * } ); 11823 */ 11824 "sInfoFiltered": "(filtered from _MAX_ total entries)", 11825 11826 11827 /** 11828 * If can be useful to append extra information to the info string at times, 11829 * and this variable does exactly that. This information will be appended to 11830 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are 11831 * being used) at all times. 11832 * @type string 11833 * @default <i>Empty string</i> 11834 * 11835 * @dtopt Language 11836 * @name DataTable.defaults.language.infoPostFix 11837 * 11838 * @example 11839 * $(document).ready( function() { 11840 * $('#example').dataTable( { 11841 * "language": { 11842 * "infoPostFix": "All records shown are derived from real information." 11843 * } 11844 * } ); 11845 * } ); 11846 */ 11847 "sInfoPostFix": "", 11848 11849 11850 /** 11851 * This decimal place operator is a little different from the other 11852 * language options since DataTables doesn't output floating point 11853 * numbers, so it won't ever use this for display of a number. Rather, 11854 * what this parameter does is modify the sort methods of the table so 11855 * that numbers which are in a format which has a character other than 11856 * a period (`.`) as a decimal place will be sorted numerically. 11857 * 11858 * Note that numbers with different decimal places cannot be shown in 11859 * the same table and still be sortable, the table must be consistent. 11860 * However, multiple different tables on the page can use different 11861 * decimal place characters. 11862 * @type string 11863 * @default 11864 * 11865 * @dtopt Language 11866 * @name DataTable.defaults.language.decimal 11867 * 11868 * @example 11869 * $(document).ready( function() { 11870 * $('#example').dataTable( { 11871 * "language": { 11872 * "decimal": "," 11873 * "thousands": "." 11874 * } 11875 * } ); 11876 * } ); 11877 */ 11878 "sDecimal": "", 11879 11880 11881 /** 11882 * DataTables has a build in number formatter (`formatNumber`) which is 11883 * used to format large numbers that are used in the table information. 11884 * By default a comma is used, but this can be trivially changed to any 11885 * character you wish with this parameter. 11886 * @type string 11887 * @default , 11888 * 11889 * @dtopt Language 11890 * @name DataTable.defaults.language.thousands 11891 * 11892 * @example 11893 * $(document).ready( function() { 11894 * $('#example').dataTable( { 11895 * "language": { 11896 * "thousands": "'" 11897 * } 11898 * } ); 11899 * } ); 11900 */ 11901 "sThousands": ",", 11902 11903 11904 /** 11905 * Detail the action that will be taken when the drop down menu for the 11906 * pagination length option is changed. The '_MENU_' variable is replaced 11907 * with a default select list of 10, 25, 50 and 100, and can be replaced 11908 * with a custom select box if required. 11909 * @type string 11910 * @default Show _MENU_ entries 11911 * 11912 * @dtopt Language 11913 * @name DataTable.defaults.language.lengthMenu 11914 * 11915 * @example 11916 * // Language change only 11917 * $(document).ready( function() { 11918 * $('#example').dataTable( { 11919 * "language": {
11920 * "lengthMenu": "Display _MENU_ records" 11921 * } 11922 * } ); 11923 * } ); 11924 * 11925 * @example 11926 * // Language and options change 11927 * $(document).ready( function() { 11928 * $('#example').dataTable( { 11929 * "language": { 11930 * "lengthMenu": 'Display <select>'+ 11931 * '<option value="10">10</option>'+ 11932 * '<option value="20">20</option>'+ 11933 * '<option value="30">30</option>'+ 11934 * '<option value="40">40</option>'+ 11935 * '<option value="50">50</option>'+ 11936 * '<option value="-1">All</option>'+ 11937 * '</select> records' 11938 * } 11939 * } ); 11940 * } ); 11941 */ 11942 "sLengthMenu": "Show _MENU_ entries", 11943 11944 11945 /** 11946 * When using Ajax sourced data and during the first draw when DataTables is 11947 * gathering the data, this message is shown in an empty row in the table to 11948 * indicate to the end user the the data is being loaded. Note that this 11949 * parameter is not used when loading data by server-side processing, just 11950 * Ajax sourced data with client-side processing. 11951 * @type string 11952 * @default Loading... 11953 * 11954 * @dtopt Language 11955 * @name DataTable.defaults.language.loadingRecords 11956 * 11957 * @example 11958 * $(document).ready( function() { 11959 * $('#example').dataTable( { 11960 * "language": { 11961 * "loadingRecords": "Please wait - loading..." 11962 * } 11963 * } ); 11964 * } ); 11965 */ 11966 "sLoadingRecords": "Loading...", 11967 11968 11969 /** 11970 * Text which is displayed when the table is processing a user action 11971 * (usually a sort command or similar). 11972 * @type string 11973 * 11974 * @dtopt Language 11975 * @name DataTable.defaults.language.processing 11976 * 11977 * @example 11978 * $(document).ready( function() { 11979 * $('#example').dataTable( { 11980 * "language": { 11981 * "processing": "DataTables is currently busy" 11982 * } 11983 * } ); 11984 * } ); 11985 */ 11986 "sProcessing": "", 11987 11988 11989 /** 11990 * Details the actions that will be taken when the user types into the 11991 * filtering input text box. The variable "_INPUT_", if used in the string, 11992 * is replaced with the HTML text box for the filtering input allowing 11993 * control over where it appears in the string. If "_INPUT_" is not given 11994 * then the input box is appended to the string automatically. 11995 * @type string 11996 * @default Search: 11997 * 11998 * @dtopt Language 11999 * @name DataTable.defaults.language.search 12000 * 12001 * @example 12002 * // Input text box will be appended at the end automatically 12003 * $(document).ready( function() { 12004 * $('#example').dataTable( { 12005 * "language": { 12006 * "search": "Filter records:" 12007 * } 12008 * } ); 12009 * } ); 12010 * 12011 * @example 12012 * // Specify where the filter should appear 12013 * $(document).ready( function() { 12014 * $('#example').dataTable( { 12015 * "language": { 12016 * "search": "Apply filter _INPUT_ to table" 12017 * } 12018 * } ); 12019 * } ); 12020 */ 12021 "sSearch": "Search:", 12022 12023 12024 /** 12025 * Assign a `placeholder` attribute to the search `input` element 12026 * @type string 12027 * @default 12028 * 12029 * @dtopt Language 12030 * @name DataTable.defaults.language.searchPlaceholder 12031 */ 12032 "sSearchPlaceholder": "", 12033 12034 12035 /** 12036 * All of the language information can be stored in a file on the 12037 * server-side, which DataTables will look up if this parameter is passed. 12038 * It must store the URL of the language file, which is in a JSON format, 12039 * and the object has the same properties as the oLanguage object in the 12040 * initialiser object (i.e. the above parameters). Please refer to one of 12041 * the example language files to see how this works in action. 12042 * @type string 12043 * @default <i>Empty string - i.e. disabled</i> 12044 * 12045 * @dtopt Language 12046 * @name DataTable.defaults.language.url 12047 * 12048 * @example 12049 * $(document).ready( function() { 12050 * $('#example').dataTable( { 12051 * "language": { 12052 * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" 12053 * } 12054 * } ); 12055 * } ); 12056 */ 12057 "sUrl": "", 12058 12059 12060 /** 12061 * Text shown inside the table records when the is no information to be 12062 * displayed after filtering. `emptyTable` is shown when there is simply no 12063 * information in the table at all (regardless of filtering). 12064 * @type string
12065 * @default No matching records found 12066 * 12067 * @dtopt Language 12068 * @name DataTable.defaults.language.zeroRecords 12069 * 12070 * @example 12071 * $(document).ready( function() { 12072 * $('#example').dataTable( { 12073 * "language": { 12074 * "zeroRecords": "No records to display" 12075 * } 12076 * } ); 12077 * } ); 12078 */ 12079 "sZeroRecords": "No matching records found" 12080 }, 12081 12082 12083 /** 12084 * This parameter allows you to have define the global filtering state at 12085 * initialisation time. As an object the `search` parameter must be 12086 * defined, but all other parameters are optional. When `regex` is true, 12087 * the search string will be treated as a regular expression, when false 12088 * (default) it will be treated as a straight string. When `smart` 12089 * DataTables will use it's smart filtering methods (to word match at 12090 * any point in the data), when false this will not be done. 12091 * @namespace 12092 * @extends DataTable.models.oSearch 12093 * 12094 * @dtopt Options 12095 * @name DataTable.defaults.search 12096 * 12097 * @example 12098 * $(document).ready( function() { 12099 * $('#example').dataTable( { 12100 * "search": {"search": "Initial search"} 12101 * } ); 12102 * } ) 12103 */ 12104 "oSearch": $.extend( {}, DataTable.models.oSearch ), 12105 12106 12107 /** 12108 * __Deprecated__ The functionality provided by this parameter has now been 12109 * superseded by that provided through `ajax`, which should be used instead. 12110 * 12111 * By default DataTables will look for the property `data` (or `aaData` for 12112 * compatibility with DataTables 1.9-) when obtaining data from an Ajax 12113 * source or for server-side processing - this parameter allows that 12114 * property to be changed. You can use Javascript dotted object notation to 12115 * get a data source for multiple levels of nesting. 12116 * @type string 12117 * @default data 12118 * 12119 * @dtopt Options 12120 * @dtopt Server-side 12121 * @name DataTable.defaults.ajaxDataProp 12122 * 12123 * @deprecated 1.10. Please use `ajax` for this functionality now. 12124 */ 12125 "sAjaxDataProp": "data", 12126 12127 12128 /** 12129 * __Deprecated__ The functionality provided by this parameter has now been 12130 * superseded by that provided through `ajax`, which should be used instead. 12131 * 12132 * You can instruct DataTables to load data from an external 12133 * source using this parameter (use aData if you want to pass data in you 12134 * already have). Simply provide a url a JSON object can be obtained from. 12135 * @type string 12136 * @default null 12137 * 12138 * @dtopt Options 12139 * @dtopt Server-side 12140 * @name DataTable.defaults.ajaxSource 12141 * 12142 * @deprecated 1.10. Please use `ajax` for this functionality now. 12143 */ 12144 "sAjaxSource": null, 12145 12146 12147 /** 12148 * This initialisation variable allows you to specify exactly where in the 12149 * DOM you want DataTables to inject the various controls it adds to the page 12150 * (for example you might want the pagination controls at the top of the 12151 * table). DIV elements (with or without a custom class) can also be added to 12152 * aid styling. The follow syntax is used: 12153 * <ul> 12154 * <li>The following options are allowed: 12155 * <ul> 12156 * <li>'l' - Length changing</li> 12157 * <li>'f' - Filtering input</li> 12158 * <li>'t' - The table!</li> 12159 * <li>'i' - Information</li> 12160 * <li>'p' - Pagination</li> 12161 * <li>'r' - pRocessing</li> 12162 * </ul> 12163 * </li> 12164 * <li>The following constants are allowed: 12165 * <ul> 12166 * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li> 12167 * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li> 12168 * </ul> 12169 * </li> 12170 * <li>The following syntax is expected: 12171 * <ul> 12172 * <li>'<' and '>' - div elements</li> 12173 * <li>'<"class" and '>' - div with a class</li> 12174 * <li>'<"#id" and '>' - div with an ID</li> 12175 * </ul> 12176 * </li> 12177 * <li>Examples: 12178 * <ul> 12179 * <li>'<"wrapper"flipt>'</li> 12180 * <li>'<lf<t>ip>'</li> 12181 * </ul> 12182 * </li> 12183 * </ul> 12184 * @type string 12185 * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b> 12186 * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i> 12187 * 12188 * @dtopt Options 12189 * @name DataTable.defaults.dom 12190 * 12191 * @example 12192 * $(document).ready( function() { 12193 * $('#example').dataTable( { 12194 * "dom": '<"top"i>rt<"bottom"flp><"clear">'
12195 * } ); 12196 * } ); 12197 */ 12198 "sDom": "lfrtip", 12199 12200 12201 /** 12202 * Search delay option. This will throttle full table searches that use the 12203 * DataTables provided search input element (it does not effect calls to 12204 * `dt-api search()`, providing a delay before the search is made. 12205 * @type integer 12206 * @default 0 12207 * 12208 * @dtopt Options 12209 * @name DataTable.defaults.searchDelay 12210 * 12211 * @example 12212 * $(document).ready( function() { 12213 * $('#example').dataTable( { 12214 * "searchDelay": 200 12215 * } ); 12216 * } ) 12217 */ 12218 "searchDelay": null, 12219 12220 12221 /** 12222 * DataTables features six different built-in options for the buttons to 12223 * display for pagination control: 12224 * 12225 * * `numbers` - Page number buttons only 12226 * * `simple` - 'Previous' and 'Next' buttons only 12227 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers 12228 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons 12229 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers 12230 * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers 12231 * 12232 * Further methods can be added using {@link DataTable.ext.oPagination}. 12233 * @type string 12234 * @default simple_numbers 12235 * 12236 * @dtopt Options 12237 * @name DataTable.defaults.pagingType 12238 * 12239 * @example 12240 * $(document).ready( function() { 12241 * $('#example').dataTable( { 12242 * "pagingType": "full_numbers" 12243 * } ); 12244 * } ) 12245 */ 12246 "sPaginationType": "simple_numbers", 12247 12248 12249 /** 12250 * Enable horizontal scrolling. When a table is too wide to fit into a 12251 * certain layout, or you have a large number of columns in the table, you 12252 * can enable x-scrolling to show the table in a viewport, which can be 12253 * scrolled. This property can be `true` which will allow the table to 12254 * scroll horizontally when needed, or any CSS unit, or a number (in which 12255 * case it will be treated as a pixel measurement). Setting as simply `true` 12256 * is recommended. 12257 * @type boolean|string 12258 * @default <i>blank string - i.e. disabled</i> 12259 * 12260 * @dtopt Features 12261 * @name DataTable.defaults.scrollX 12262 * 12263 * @example 12264 * $(document).ready( function() { 12265 * $('#example').dataTable( { 12266 * "scrollX": true, 12267 * "scrollCollapse": true 12268 * } ); 12269 * } ); 12270 */ 12271 "sScrollX": "", 12272 12273 12274 /** 12275 * This property can be used to force a DataTable to use more width than it 12276 * might otherwise do when x-scrolling is enabled. For example if you have a 12277 * table which requires to be well spaced, this parameter is useful for 12278 * "over-sizing" the table, and thus forcing scrolling. This property can by 12279 * any CSS unit, or a number (in which case it will be treated as a pixel 12280 * measurement). 12281 * @type string 12282 * @default <i>blank string - i.e. disabled</i> 12283 * 12284 * @dtopt Options 12285 * @name DataTable.defaults.scrollXInner 12286 * 12287 * @example 12288 * $(document).ready( function() { 12289 * $('#example').dataTable( { 12290 * "scrollX": "100%", 12291 * "scrollXInner": "110%" 12292 * } ); 12293 * } ); 12294 */ 12295 "sScrollXInner": "", 12296 12297 12298 /** 12299 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable 12300 * to the given height, and enable scrolling for any data which overflows the 12301 * current viewport. This can be used as an alternative to paging to display 12302 * a lot of data in a small area (although paging and scrolling can both be 12303 * enabled at the same time). This property can be any CSS unit, or a number 12304 * (in which case it will be treated as a pixel measurement). 12305 * @type string 12306 * @default <i>blank string - i.e. disabled</i> 12307 * 12308 * @dtopt Features 12309 * @name DataTable.defaults.scrollY 12310 * 12311 * @example 12312 * $(document).ready( function() { 12313 * $('#example').dataTable( { 12314 * "scrollY": "200px", 12315 * "paginate": false 12316 * } ); 12317 * } ); 12318 */ 12319 "sScrollY": "", 12320 12321 12322 /** 12323 * __Deprecated__ The functionality provided by this parameter has now been 12324 * superseded by that provided through `ajax`, which should be used instead. 12325 * 12326 * Set the HTTP method that is used to make the Ajax call for server-side 12327 * processing or Ajax sourced data. 12328 * @type string 12329 * @default GET 12330 * 12331 * @dtopt Options 12332 * @dtopt Server-side 12333 * @name DataTable.defaults.serverMethod 12334 * 12335 * @deprecated 1.10. Please use `ajax` for this functionality now. 12336 */ 12337 "sServerMethod": "GET", 12338 12339 12340 /** 12341 * DataTables makes use of renderers when displaying HTML elements for 12342 * a table. These renderers can be added or modified by plug-ins to 12343 * generate suitable mark-up for a site. For example the Bootstrap 12344 * integration plug-in for DataTables uses a paging button renderer to 12345 * display pagination buttons in the mark-up required by Bootstrap. 12346 * 12347 * For further information about the renderers available see 12348 * DataTable.ext.renderer 12349 * @type string|object 12350 * @default null 12351 * 12352 * @name DataTable.defaults.renderer 12353 * 12354 */ 12355 "renderer": null, 12356 12357 12358 /** 12359 * Set the data property name that DataTables should use to get a row's id 12360 * to set as the `id` property in the node. 12361 * @type string 12362 * @default DT_RowId 12363 * 12364 * @name DataTable.defaults.rowId 12365 */ 12366 "rowId": "DT_RowId" 12367 }; 12368 12369 _fnHungarianMap( DataTable.defaults ); 12370 12371 12372 12373 /* 12374 * Developer note - See note in model.defaults.js about the use of Hungarian 12375 * notation and camel case. 12376 */ 12377 12378 /** 12379 * Column options that can be given to DataTables at initialisation time. 12380 * @namespace 12381 */ 12382 DataTable.defaults.column = { 12383 /** 12384 * Define which column(s) an order will occur on for this column. This 12385 * allows a column's ordering to take multiple columns into account when
12386 * doing a sort or use the data from a different column. For example first 12387 * name / last name columns make sense to do a multi-column sort over the 12388 * two columns. 12389 * @type array|int 12390 * @default null <i>Takes the value of the column index automatically</i> 12391 * 12392 * @name DataTable.defaults.column.orderData 12393 * @dtopt Columns 12394 * 12395 * @example 12396 * // Using `columnDefs` 12397 * $(document).ready( function() { 12398 * $('#example').dataTable( { 12399 * "columnDefs": [ 12400 * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, 12401 * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, 12402 * { "orderData": 2, "targets": [ 2 ] } 12403 * ] 12404 * } ); 12405 * } ); 12406 * 12407 * @example 12408 * // Using `columns` 12409 * $(document).ready( function() { 12410 * $('#example').dataTable( { 12411 * "columns": [ 12412 * { "orderData": [ 0, 1 ] }, 12413 * { "orderData": [ 1, 0 ] }, 12414 * { "orderData": 2 }, 12415 * null, 12416 * null 12417 * ] 12418 * } ); 12419 * } ); 12420 */ 12421 "aDataSort": null, 12422 "iDataSort": -1, 12423 12424 12425 /** 12426 * You can control the default ordering direction, and even alter the 12427 * behaviour of the sort handler (i.e. only allow ascending ordering etc) 12428 * using this parameter. 12429 * @type array 12430 * @default [ 'asc', 'desc' ] 12431 * 12432 * @name DataTable.defaults.column.orderSequence 12433 * @dtopt Columns 12434 * 12435 * @example 12436 * // Using `columnDefs` 12437 * $(document).ready( function() { 12438 * $('#example').dataTable( { 12439 * "columnDefs": [ 12440 * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, 12441 * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, 12442 * { "orderSequence": [ "desc" ], "targets": [ 3 ] } 12443 * ] 12444 * } ); 12445 * } ); 12446 * 12447 * @example 12448 * // Using `columns` 12449 * $(document).ready( function() { 12450 * $('#example').dataTable( { 12451 * "columns": [ 12452 * null, 12453 * { "orderSequence": [ "asc" ] }, 12454 * { "orderSequence": [ "desc", "asc", "asc" ] }, 12455 * { "orderSequence": [ "desc" ] }, 12456 * null 12457 * ] 12458 * } ); 12459 * } ); 12460 */ 12461 "asSorting": [ 'asc', 'desc' ], 12462 12463 12464 /** 12465 * Enable or disable filtering on the data in this column. 12466 * @type boolean 12467 * @default true 12468 * 12469 * @name DataTable.defaults.column.searchable 12470 * @dtopt Columns 12471 * 12472 * @example 12473 * // Using `columnDefs` 12474 * $(document).ready( function() { 12475 * $('#example').dataTable( { 12476 * "columnDefs": [ 12477 * { "searchable": false, "targets": [ 0 ] } 12478 * ] } ); 12479 * } ); 12480 * 12481 * @example 12482 * // Using `columns` 12483 * $(document).ready( function() { 12484 * $('#example').dataTable( { 12485 * "columns": [ 12486 * { "searchable": false }, 12487 * null, 12488 * null, 12489 * null, 12490 * null 12491 * ] } ); 12492 * } ); 12493 */ 12494 "bSearchable": true, 12495 12496 12497 /** 12498 * Enable or disable ordering on this column. 12499 * @type boolean 12500 * @default true 12501 * 12502 * @name DataTable.defaults.column.orderable 12503 * @dtopt Columns 12504 * 12505 * @example 12506 * // Using `columnDefs` 12507 * $(document).ready( function() { 12508 * $('#example').dataTable( { 12509 * "columnDefs": [ 12510 * { "orderable": false, "targets": [ 0 ] } 12511 * ] } ); 12512 * } ); 12513 * 12514 * @example 12515 * // Using `columns` 12516 * $(document).ready( function() { 12517 * $('#example').dataTable( { 12518 * "columns": [ 12519 * { "orderable": false }, 12520 * null, 12521 * null, 12522 * null, 12523 * null 12524 * ] } ); 12525 * } ); 12526 */ 12527 "bSortable": true, 12528 12529 12530 /** 12531 * Enable or disable the display of this column. 12532 * @type boolean 12533 * @default true 12534 * 12535 * @name DataTable.defaults.column.visible 12536 * @dtopt Columns 12537 * 12538 * @example 12539 * // Using `columnDefs` 12540 * $(document).ready( function() { 12541 * $('#example').dataTable( { 12542 * "columnDefs": [ 12543 * { "visible": false, "targets": [ 0 ] } 12544 * ] } ); 12545 * } ); 12546 * 12547 * @example 12548 * // Using `columns` 12549 * $(document).ready( function() { 12550 * $('#example').dataTable( { 12551 * "columns": [ 12552 * { "visible": false }, 12553 * null, 12554 * null, 12555 * null, 12556 * null 12557 * ] } ); 12558 * } ); 12559 */ 12560 "bVisible": true, 12561 12562 12563 /** 12564 * Developer definable function that is called whenever a cell is created (Ajax source, 12565 * etc) or processed for input (DOM source). This can be used as a compliment to mRender 12566 * allowing you to modify the DOM element (add background colour for example) when the 12567 * element is available. 12568 * @type function 12569 * @param {element} td The TD node that has been created 12570 * @param {*} cellData The Data for the cell 12571 * @param {array|object} rowData The data for the whole row 12572 * @param {int} row The row index for the aoData data store 12573 * @param {int} col The column index for aoColumns 12574 * 12575 * @name DataTable.defaults.column.createdCell 12576 * @dtopt Columns 12577 * 12578 * @example 12579 * $(document).ready( function() { 12580 * $('#example').dataTable( { 12581 * "columnDefs": [ { 12582 * "targets": [3], 12583 * "createdCell": function (td, cellData, rowData, row, c
12583ol) { 12584 * if ( cellData == "1.7" ) { 12585 * $(td).css('color', 'blue') 12586 * } 12587 * } 12588 * } ] 12589 * }); 12590 * } ); 12591 */ 12592 "fnCreatedCell": null, 12593 12594 12595 /** 12596 * This parameter has been replaced by `data` in DataTables to ensure naming 12597 * consistency. `dataProp` can still be used, as there is backwards 12598 * compatibility in DataTables for this option, but it is strongly 12599 * recommended that you use `data` in preference to `dataProp`. 12600 * @name DataTable.defaults.column.dataProp 12601 */ 12602 12603 12604 /** 12605 * This property can be used to read data from any data source property, 12606 * including deeply nested objects / properties. `data` can be given in a 12607 * number of different ways which effect its behaviour: 12608 * 12609 * * `integer` - treated as an array index for the data source. This is the 12610 * default that DataTables uses (incrementally increased for each column). 12611 * * `string` - read an object property from the data source. There are 12612 * three 'special' options that can be used in the string to alter how 12613 * DataTables reads the data from the source object: 12614 * * `.` - Dotted Javascript notation. Just as you use a `.` in 12615 * Javascript to read from nested objects, so to can the options 12616 * specified in `data`. For example: `browser.version` or 12617 * `browser.name`. If your object parameter name contains a period, use 12618 * `\\` to escape it - i.e. `first\\.name`. 12619 * * `[]` - Array notation. DataTables can automatically combine data 12620 * from and array source, joining the data with the characters provided 12621 * between the two brackets. For example: `name[, ]` would provide a 12622 * comma-space separated list from the source array. If no characters 12623 * are provided between the brackets, the original array source is 12624 * returned. 12625 * * `()` - Function notation. Adding `()` to the end of a parameter will 12626 * execute a function of the name given. For example: `browser()` for a 12627 * simple function on the data source, `browser.version()` for a 12628 * function in a nested property or even `browser().version` to get an 12629 * object property if the function called returns an object. Note that 12630 * function notation is recommended for use in `render` rather than 12631 * `data` as it is much simpler to use as a renderer. 12632 * * `null` - use the original data source for the row rather than plucking 12633 * data directly from it. This action has effects on two other 12634 * initialisation options: 12635 * * `defaultContent` - When null is given as the `data` option and 12636 * `defaultContent` is specified for the column, the value defined by 12637 * `defaultContent` will be used for the cell. 12638 * * `render` - When null is used for the `data` option and the `render` 12639 * option is specified for the column, the whole data source for the 12640 * row is used for the renderer. 12641 * * `function` - the function given will be executed whenever DataTables 12642 * needs to set or get the data for a cell in the column. The function 12643 * takes three parameters: 12644 * * Parameters: 12645 * * `{array|object}` The data source for the row 12646 * * `{string}` The type call data requested - this will be 'set' when 12647 * setting data or 'filter', 'display', 'type', 'sort' or undefined 12648 * when gathering data. Note that when `undefined` is given for the 12649 * type DataTables expects to get the raw data for the object back< 12650 * * `{*}` Data to set when the second parameter is 'set'. 12651 * * Return: 12652 * * The return value from the function is not required when 'set' is 12653 * the type of call, but otherwise the return is what will be used 12654 * for the data requested. 12655 * 12656 * Note that `data` is a getter and setter option. If you just require 12657 * formatting of data for output, you will likely want to use `render` which 12658 * is simply a getter and thus simpler to use. 12659 * 12660 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The 12661 * name change reflects the flexibility of this property and is consistent 12662 * with the naming of mRender. If 'mDataProp' is given, then it will still 12663 * be used by DataTables, as it automatically maps the old name to the new 12664 * if required. 12665 * 12666 * @type string|int|function|null 12667 * @default null <i>Use automatically calculated column index</i> 12668 * 12669 * @name DataTable.defaults.column.data 12670 * @dtopt Columns 12671 * 12672 * @example 12673 * // Read table data from objects 12674 * // JSON structure for each row: 12675 * // { 12676 * // "engine": {value}, 12677 * // "browser": {value}, 12678 * // "platform": {value}, 12679 * // "version": {value}, 12680 * // "grade": {value} 12681 * // } 12682 * $(document).ready( function() { 12683 * $('#example').dataTable( { 12684 * "ajaxSource": "sources/objects.txt", 12685 * "columns": [ 12686 * { "data": "engine" }, 12687 * { "data": "browser" }, 12688 * { "data": "platform" }, 12689 * { "data": "version" }, 12690 * { "data": "grade" } 12691 * ] 12692 * } ); 12693 * } ); 12694 * 12695 * @example 12696 * // Read information from deeply nested objects 12697 * // JSON structure for each row: 12698 * // { 12699 * // "engine": {value}, 12700 * // "browser": {value}, 12701 * // "platform": { 12702 * // "inner": {value} 12703 * // }, 12704 * // "details": [ 12705 * // {value}, {value} 12706 * // ] 12707 * // } 12708 * $(document).ready( function() { 12709 * $('#example').dataTable( { 12710 * "ajaxSource": "sources/deep.txt", 12711 * "columns": [ 12712 * { "data": "engine" }, 12713 * { "data": "browser" }, 12714 * { "data": "platform.inner" }, 12715 * { "data": "details.0" }, 12716 * { "data": "details.1" } 12717 * ] 12718 * } ); 12719 * } ); 12720 * 12721 * @example 12722 * // Using `data` as a function to provide different information for 12723 * // sorting, filtering and display. In this case, currency (price) 12724 * $(document).ready( function() { 12725 * $('#example').dataTable( { 12726 * "columnDefs": [ { 12727 * "targets": [ 0 ], 12728 * "data": function ( source, type, val ) { 12729 * if (type === 'set') { 12730 * source.price = val; 12731 * // Store the computed display and filter values for efficiency 12732 * source.price_display = val=="" ? "" : "$"+numberFormat(val); 12733 * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; 12734 * return; 12735 * } 12736 * else if (type === 'display') { 12737 * return source.price_display; 12738 * } 12739 * else if (type === 'filter') { 12740 * return source.price_filter; 12741 * } 12742 * // 'sort', 'type' and undefined all just use the integer 12743 * return source.price; 12744 * } 12745 * } ] 12746 * } ); 12747 * } ); 12748 * 12749 * @example 12750 * // Using default content 12751 * $(document).ready( function() { 12752 * $('#example').dataTable( { 12753 * "columnDefs": [ { 12754 * "targets": [ 0 ], 12755 * "data": null, 12756 * "defaultContent": "Click to edit" 12757 * } ] 12758 * } ); 12759 * } ); 12760 * 12761 * @example 12762 * // Using array notation - outputting a list from an array 12763 * $(document).ready( function() { 12764 * $('#example').dataTable( { 12765 * "columnDefs": [ { 12766 * "targets": [ 0 ], 12767 * "data": "name[, ]" 12768 * } ] 12769 * } ); 12770 * } ); 12771 * 12772 */ 12773 "mData": null, 12774 12775 12776 /** 12777 * This property is the rendering partner to `data` and it is suggested that 12778 * when you want to manipulate data for display (including filtering, 12779 * sorting etc) without altering the underlying data for the table, use this 12780 * property. `render` can be considered to be the the read only companion to 12781 * `data` which is read / write (then as such more complex). Like `data` 12782 * this option can be given in a number of different ways to effect its 12783 * behaviour: 12784 * 12785 * * `integer` - treated as an array index for the data source. This is the 12786 * default that DataTables uses (incrementally increased for each column). 12787 * * `string` - read an object property from the data source. There are 12788 * three 'special' options that can be used in the string to alter how 12789 * DataTables reads the data from the source object: 12790 * * `.` - Dotted Javascript notation. Just as you use a `.` in 12791 * Javascript to read from nested objects, so to can the options 12792 * specified in `data`. For example: `browser.version` or 12793 * `browser.name`. If your object parameter name contains a period, use 12794 * `\\` to escape it - i.e. `first\\.name`. 12795 * * `[]` - Array notation. DataTables can automatically combine data 12796 * from and array source, joining the data with the characters provided 12797 * between the two brackets. For example: `name[, ]` would provide a 12798 * comma-space separated list from the source array. If no characters 12799 * are provided between the brackets, the original array source is 12800 * returned. 12801 * * `()` - Function notation. Adding `()` to the end of a parameter will 12802 * execute a function of the name given. For example: `browser()` for a 12803 * simple function on the data source, `browser.version()` for a 12804 * function in a nested property or even `browser().version` to get an 12805 * object property if the function called returns an object. 12806 * * `object` - use different data for the different data types requested by 12807 * DataTables ('filter', 'display', 'type' or 'sort'). The property names 12808 * of the object is the data type the property refers to and the value can 12809 * defined using an integer, string or function using the same rules as 12810 * `render` normally does. Note that an `_` option _must_ be specified. 12811 * This is the default value to use if you haven't specified a value for 12812 * the data type requested by DataTables. 12813 * * `function` - the function given will be executed whenever DataTables 12814 * needs to set or get the data for a cell in the column. The function 12815 * takes three parameters: 12816 * * Parameters: 12817 * * {array|object} The data source for the row (based on `data`) 12818 * * {string} The type call data requested - this will be 'filter', 12819 * 'display', 'type' or 'sort'. 12820 * * {array|object} The full data source for the row (not based on 12821 * `data`) 12822 * * Return: 12823 * * The return value from the function is what will be used for the 12824 * data requested. 12825 * 12826 * @type string|int|function|object|null 12827 * @default null Use the data source value. 12828 * 12829 * @name DataTable.defaults.column.render 12830 * @dtopt Columns 12831 * 12832 * @example 12833 * // Create a comma separated list from an array of objects 12834 * $(document).ready( function() { 12835 * $('#example').dataTable( { 12836 * "ajaxSource": "sources/deep.txt", 12837 * "columns": [ 12838 * { "data": "engine" }, 12839 * { "data": "browser" }, 12840 * { 12841 * "data": "platform", 12842 * "render": "[, ].name" 12843 * } 12844 * ] 12845 * } ); 12846 * } ); 12847 * 12848 * @example 12849 * // Execute a function to obtain data 12850 * $(document).ready( function() { 12851 * $('#example').dataTable( { 12852 * "columnDefs": [ { 12853 * "targets": [ 0 ], 12854 * "data": null, // Use the full data source object for the renderer's source 12855 * "render": "browserName()" 12856 * } ] 12857 * } ); 12858 * } ); 12859 * 12860 * @example 12861 * // As an object, extracting different data for the different types 12862 * // This would be used with a data source such as: 12863 * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } 12864 * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` 12865 * // (which has both forms) is used for filtering for if a user inputs either format, while 12866 * // the formatted phone number is the one that is shown in the table. 12867 * $(document).ready( function() { 12868 * $('#example').dataTable( { 12869 * "columnDefs": [ { 12870 * "targets": [ 0 ], 12871 * "data": null, // Use the full data source object for the renderer's source 12872 * "render": { 12873 * "_": "phone", 12874 * "filter": "phone_filter", 12875 * "display": "phone_display" 12876 * } 12877 * } ] 12878 * } ); 12879 * } ); 12880 * 12881 * @example 12882 * // Use as a function to create a link from the data source 12883 * $(document).ready( function() { 12884 * $('#example').dataTable( { 12885 * "columnDefs": [ { 12886 * "targets": [ 0 ], 12887 * "data": "download_link", 12888 * "render": function ( data, type, full ) { 12889 * return '<a href="'+data+'">Download</a>'; 12890 * } 12891 * } ] 12892 * } ); 12893 * } ); 12894 */ 12895 "mRender": null, 12896 12897 12898 /** 12899 * Change the cell type created for the column - either TD cells or TH cells. This 12900 * can be useful as TH cells have semantic meaning in the table body, allowing them 12901 * to act as a header for a row (you may wish to add scope='row' to the TH elements). 12902 * @type string 12903 * @default td 12904 * 12905 * @name DataTable.defaults.column.cellType
12906 * @dtopt Columns 12907 * 12908 * @example 12909 * // Make the first column use TH cells 12910 * $(document).ready( function() { 12911 * $('#example').dataTable( { 12912 * "columnDefs": [ { 12913 * "targets": [ 0 ], 12914 * "cellType": "th" 12915 * } ] 12916 * } ); 12917 * } ); 12918 */ 12919 "sCellType": "td", 12920 12921 12922 /** 12923 * Class to give to each cell in this column. 12924 * @type string 12925 * @default <i>Empty string</i> 12926 * 12927 * @name DataTable.defaults.column.class 12928 * @dtopt Columns 12929 * 12930 * @example 12931 * // Using `columnDefs` 12932 * $(document).ready( function() { 12933 * $('#example').dataTable( { 12934 * "columnDefs": [ 12935 * { "class": "my_class", "targets": [ 0 ] } 12936 * ] 12937 * } ); 12938 * } ); 12939 * 12940 * @example 12941 * // Using `columns` 12942 * $(document).ready( function() { 12943 * $('#example').dataTable( { 12944 * "columns": [ 12945 * { "class": "my_class" }, 12946 * null, 12947 * null, 12948 * null, 12949 * null 12950 * ] 12951 * } ); 12952 * } ); 12953 */ 12954 "sClass": "", 12955 12956 /** 12957 * When DataTables calculates the column widths to assign to each column, 12958 * it finds the longest string in each column and then constructs a 12959 * temporary table and reads the widths from that. The problem with this 12960 * is that "mmm" is much wider then "iiii", but the latter is a longer 12961 * string - thus the calculation can go wrong (doing it properly and putting 12962 * it into an DOM object and measuring that is horribly(!) slow). Thus as 12963 * a "work around" we provide this option. It will append its value to the 12964 * text that is found to be the longest string for the column - i.e. padding. 12965 * Generally you shouldn't need this! 12966 * @type string 12967 * @default <i>Empty string<i> 12968 * 12969 * @name DataTable.defaults.column.contentPadding 12970 * @dtopt Columns 12971 * 12972 * @example 12973 * // Using `columns` 12974 * $(document).ready( function() { 12975 * $('#example').dataTable( { 12976 * "columns": [ 12977 * null, 12978 * null, 12979 * null, 12980 * { 12981 * "contentPadding": "mmm" 12982 * } 12983 * ] 12984 * } ); 12985 * } ); 12986 */ 12987 "sContentPadding": "", 12988 12989 12990 /** 12991 * Allows a default value to be given for a column's data, and will be used 12992 * whenever a null data source is encountered (this can be because `data` 12993 * is set to null, or because the data source itself is null). 12994 * @type string 12995 * @default null 12996 * 12997 * @name DataTable.defaults.column.defaultContent 12998 * @dtopt Columns 12999 * 13000 * @example 13001 * // Using `columnDefs` 13002 * $(document).ready( function() { 13003 * $('#example').dataTable( { 13004 * "columnDefs": [ 13005 * { 13006 * "data": null, 13007 * "defaultContent": "Edit", 13008 * "targets": [ -1 ] 13009 * } 13010 * ] 13011 * } ); 13012 * } ); 13013 * 13014 * @example 13015 * // Using `columns` 13016 * $(document).ready( function() { 13017 * $('#example').dataTable( { 13018 * "columns": [ 13019 * null, 13020 * null, 13021 * null, 13022 * { 13023 * "data": null, 13024 * "defaultContent": "Edit" 13025 * } 13026 * ] 13027 * } ); 13028 * } ); 13029 */ 13030 "sDefaultContent": null, 13031 13032 13033 /** 13034 * This parameter is only used in DataTables' server-side processing. It can 13035 * be exceptionally useful to know what columns are being displayed on the 13036 * client side, and to map these to database fields. When defined, the names 13037 * also allow DataTables to reorder information from the server if it comes 13038 * back in an unexpected order (i.e. if you switch your columns around on the 13039 * client-side, your server-side code does not also need updating). 13040 * @type string 13041 * @default <i>Empty string</i> 13042 * 13043 * @name DataTable.defaults.column.name 13044 * @dtopt Columns 13045 * 13046 * @example 13047 * // Using `columnDefs` 13048 * $(document).ready( function() { 13049 * $('#example').dataTable( { 13050 * "columnDefs": [ 13051 * { "name": "engine", "targets": [ 0 ] }, 13052 * { "name": "browser", "targets": [ 1 ] }, 13053 * { "name": "platform", "targets": [ 2 ] }, 13054 * { "name": "version", "targets": [ 3 ] }, 13055 * { "name": "grade", "targets": [ 4 ] } 13056 * ] 13057 * } ); 13058 * } ); 13059 * 13060 * @example 13061 * // Using `columns` 13062 * $(document).ready( function() { 13063 * $('#example').dataTable( { 13064 * "columns": [ 13065 * { "name": "engine" }, 13066 * { "name": "browser" }, 13067 * { "name": "platform" }, 13068 * { "name": "version" }, 13069 * { "name": "grade" } 13070 * ] 13071 * } ); 13072 * } ); 13073 */ 13074 "sName": "", 13075 13076 13077 /** 13078 * Defines a data source type for the ordering which can be used to read 13079 * real-time information from the table (updating the internally cached 13080 * version) prior to ordering. This allows ordering to occur on user 13081 * editable elements such as form inputs. 13082 * @type string 13083 * @default std 13084 * 13085 * @name DataTable.defaults.column.orderDataType 13086 * @dtopt Columns 13087 * 13088 * @example 13089 * // Using `columnDefs` 13090 * $(document).ready( function() { 13091 * $('#example').dataTable( { 13092 * "columnDefs": [ 13093 * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, 13094 * { "type": "numeric", "targets": [ 3 ] }, 13095 * { "orderDataType": "dom-select", "targets": [ 4 ] }, 13096 * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } 13097 * ] 13098 * } ); 13099 * } ); 13100 * 13101 * @example 13102 * // Using `columns` 13103 * $(document).ready( function() { 13104 * $('#example').dataTable( { 13105 * "columns": [ 13106 * null, 13107 * null, 13108 * { "orderDataType": "dom-text" }, 13109 * { "orderDataType": "dom-text", "type": "numeric" }, 13110 * { "orderDataType": "dom-select" }, 13111 * { "orderDataType": "dom-checkbox" } 13112 * ] 13113 * } ); 13114 * } ); 13115 */ 13116 "sSortDataType": "std", 13117 13118 13119 /** 13120 * The title of this column. 13121 * @type string 13122 * @default null <i>Derived from the 'TH' value for this column in the 13123 * original HTML table.</i> 13124 * 13125 * @name DataTable.defaults.column.title 13126 * @dtopt Columns 13127 * 13128 * @example 13129 * // Using `columnDefs` 13130 * $(document).ready( function() { 13131 * $('#example').dataTable( { 13132 * "columnDefs": [ 13133 * { "title": "My column title", "targets": [ 0 ] } 13134 * ] 13135 * } ); 13136 * } ); 13137 * 13138 * @example 13139 * // Using `columns` 13140 * $(document).ready( function() { 13141 * $('#example').dataTable( { 13142 * "columns": [ 13143 * { "title": "My column title" }, 13144 * null, 13145 * null, 13146 * null, 13147 * null 13148 * ] 13149 * } ); 13150 * } ); 13151 */ 13152 "sTitle": null, 13153 13154 13155 /** 13156 * The type allows you to specify how the data for this column will be
13157 * ordered. Four types (string, numeric, date and html (which will strip 13158 * HTML tags before ordering)) are currently available. Note that only date 13159 * formats understood by Javascript's Date() object will be accepted as type 13160 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', 13161 * 'numeric', 'date' or 'html' (by default). Further types can be adding 13162 * through plug-ins. 13163 * @type string 13164 * @default null <i>Auto-detected from raw data</i> 13165 * 13166 * @name DataTable.defaults.column.type 13167 * @dtopt Columns 13168 * 13169 * @example 13170 * // Using `columnDefs` 13171 * $(document).ready( function() { 13172 * $('#example').dataTable( { 13173 * "columnDefs": [ 13174 * { "type": "html", "targets": [ 0 ] } 13175 * ] 13176 * } ); 13177 * } ); 13178 * 13179 * @example 13180 * // Using `columns` 13181 * $(document).ready( function() { 13182 * $('#example').dataTable( { 13183 * "columns": [ 13184 * { "type": "html" }, 13185 * null, 13186 * null, 13187 * null, 13188 * null 13189 * ] 13190 * } ); 13191 * } ); 13192 */ 13193 "sType": null, 13194 13195 13196 /** 13197 * Defining the width of the column, this parameter may take any CSS value 13198 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not 13199 * been given a specific width through this interface ensuring that the table 13200 * remains readable. 13201 * @type string 13202 * @default null <i>Automatic</i> 13203 * 13204 * @name DataTable.defaults.column.width 13205 * @dtopt Columns 13206 * 13207 * @example 13208 * // Using `columnDefs` 13209 * $(document).ready( function() { 13210 * $('#example').dataTable( { 13211 * "columnDefs": [ 13212 * { "width": "20%", "targets": [ 0 ] } 13213 * ] 13214 * } ); 13215 * } ); 13216 * 13217 * @example 13218 * // Using `columns` 13219 * $(document).ready( function() { 13220 * $('#example').dataTable( { 13221 * "columns": [ 13222 * { "width": "20%" }, 13223 * null, 13224 * null, 13225 * null, 13226 * null 13227 * ] 13228 * } ); 13229 * } ); 13230 */ 13231 "sWidth": null 13232 }; 13233 13234 _fnHungarianMap( DataTable.defaults.column ); 13235 13236 13237 13238 /** 13239 * DataTables settings object - this holds all the information needed for a 13240 * given table, including configuration, data and current application of the 13241 * table options. DataTables does not have a single instance for each DataTable 13242 * with the settings attached to that instance, but rather instances of the 13243 * DataTable "class" are created on-the-fly as needed (typically by a 13244 * $().dataTable() call) and the settings object is then applied to that 13245 * instance. 13246 * 13247 * Note that this object is related to {@link DataTable.defaults} but this 13248 * one is the internal data store for DataTables's cache of columns. It should 13249 * NOT be manipulated outside of DataTables. Any configuration should be done 13250 * through the initialisation options. 13251 * @namespace 13252 * @todo Really should attach the settings object to individual instances so we 13253 * don't need to create new instances on each $().dataTable() call (if the 13254 * table already exists). It would also save passing oSettings around and 13255 * into every single function. However, this is a very significant 13256 * architecture change for DataTables and will almost certainly break 13257 * backwards compatibility with older installations. This is something that 13258 * will be done in 2.0. 13259 */ 13260 DataTable.models.oSettings = { 13261 /** 13262 * Primary features of DataTables and their enablement state. 13263 * @namespace 13264 */ 13265 "oFeatures": { 13266 13267 /** 13268 * Flag to say if DataTables should automatically try to calculate the 13269 * optimum table and columns widths (true) or not (false). 13270 * Note that this parameter will be set by the initialisation routine. To 13271 * set a default use {@link DataTable.defaults}. 13272 * @type boolean 13273 */ 13274 "bAutoWidth": null, 13275 13276 /** 13277 * Delay the creation of TR and TD elements until they are actually 13278 * needed by a driven page draw. This can give a significant speed 13279 * increase for Ajax source and Javascript source data, but makes no 13280 * difference at all for DOM and server-side processing tables. 13281 * Note that this parameter will be set by the initialisation routine. To 13282 * set a default use {@link DataTable.defaults}. 13283 * @type boolean 13284 */ 13285 "bDeferRender": null, 13286 13287 /** 13288 * Enable filtering on the table or not. Note that if this is disabled 13289 * then there is no filtering at all on the table, including fnFilter. 13290 * To just remove the filtering input use sDom and remove the 'f' option. 13291 * Note that this parameter will be set by the initialisation routine. To 13292 * set a default use {@link DataTable.defaults}. 13293 * @type boolean 13294 */ 13295 "bFilter": null, 13296 13297 /** 13298 * Table information element (the 'Showing x of y records' div) enable 13299 * flag. 13300 * Note that this parameter will be set by the initialisation routine. To 13301 * set a default use {@link DataTable.defaults}. 13302 * @type boolean 13303 */ 13304 "bInfo": null, 13305 13306 /** 13307 * Present a user control allowing the end user to change the page size 13308 * when pagination is enabled. 13309 * Note that this parameter will be set by the initialisation routine. To 13310 * set a default use {@link DataTable.defaults}. 13311 * @type boolean 13312 */ 13313 "bLengthChange": null, 13314 13315 /** 13316 * Pagination enabled or not. Note that if this is disabled then length 13317 * changing must also be disabled. 13318 * Note that this parameter will be set by the initialisation routine. To 13319 * set a default use {@link DataTable.defaults}. 13320 * @type boolean 13321 */ 13322 "bPaginate": null, 13323 13324 /** 13325 * Processing indicator enable flag whenever DataTables is enacting a 13326 * user request - typically an Ajax request for server-side processing. 13327 * Note that this parameter will be set by the initialisation routine. To 13328 * set a default use {@link DataTable.defaults}. 13329 * @type boolean 13330 */ 13331 "bProcessing": null, 13332 13333 /** 13334 * Server-side processing enabled flag - when enabled DataTables will 13335 * get all data from the server for every draw - there is no filtering, 13336 * sorting or paging done on the client-side. 13337 * Note that this parameter will be set by the initialisation routine. To 13338 * set a default use {@link DataTable.defaults}. 13339 * @type boolean 13340 */ 13341 "bServerSide": null, 13342 13343 /** 13344 * Sorting enablement flag. 13345 * Note that this parameter will be set by the initialisation routine. To 13346 * set a default use {@link DataTable.defaults}. 13347 * @type boolean 13348 */ 13349 "bSort": null, 13350 13351 /**
vendor: 10,013 bytes, lines 13352-13760
13352 * Multi-column sorting 13353 * Note that this parameter will be set by the initialisation routine. To 13354 * set a default use {@link DataTable.defaults}. 13355 * @type boolean 13356 */ 13357 "bSortMulti": null, 13358 13359 /** 13360 * Apply a class to the columns which are being sorted to provide a 13361 * visual highlight or not. This can slow things down when enabled since 13362 * there is a lot of DOM interaction. 13363 * Note that this parameter will be set by the initialisation routine. To 13364 * set a default use {@link DataTable.defaults}. 13365 * @type boolean 13366 */ 13367 "bSortClasses": null, 13368 13369 /** 13370 * State saving enablement flag. 13371 * Note that this parameter will be set by the initialisation routine. To 13372 * set a default use {@link DataTable.defaults}. 13373 * @type boolean 13374 */ 13375 "bStateSave": null 13376 }, 13377 13378 13379 /** 13380 * Scrolling settings for a table. 13381 * @namespace 13382 */ 13383 "oScroll": { 13384 /** 13385 * When the table is shorter in height than sScrollY, collapse the 13386 * table container down to the height of the table (when true). 13387 * Note that this parameter will be set by the initialisation routine. To 13388 * set a default use {@link DataTable.defaults}. 13389 * @type boolean 13390 */ 13391 "bCollapse": null, 13392 13393 /** 13394 * Width of the scrollbar for the web-browser's platform. Calculated 13395 * during table initialisation. 13396 * @type int 13397 * @default 0 13398 */ 13399 "iBarWidth": 0, 13400 13401 /** 13402 * Viewport width for horizontal scrolling. Horizontal scrolling is 13403 * disabled if an empty string. 13404 * Note that this parameter will be set by the initialisation routine. To 13405 * set a default use {@link DataTable.defaults}. 13406 * @type string 13407 */ 13408 "sX": null, 13409 13410 /** 13411 * Width to expand the table to when using x-scrolling. Typically you 13412 * should not need to use this. 13413 * Note that this parameter will be set by the initialisation routine. To 13414 * set a default use {@link DataTable.defaults}. 13415 * @type string 13416 * @deprecated 13417 */ 13418 "sXInner": null, 13419 13420 /** 13421 * Viewport height for vertical scrolling. Vertical scrolling is disabled 13422 * if an empty string. 13423 * Note that this parameter will be set by the initialisation routine. To 13424 * set a default use {@link DataTable.defaults}. 13425 * @type string 13426 */ 13427 "sY": null 13428 }, 13429 13430 /** 13431 * Language information for the table. 13432 * @namespace 13433 * @extends DataTable.defaults.oLanguage 13434 */ 13435 "oLanguage": { 13436 /** 13437 * Information callback function. See 13438 * {@link DataTable.defaults.fnInfoCallback} 13439 * @type function 13440 * @default null 13441 */ 13442 "fnInfoCallback": null 13443 }, 13444 13445 /** 13446 * Browser support parameters 13447 * @namespace 13448 */ 13449 "oBrowser": { 13450 /** 13451 * Indicate if the browser incorrectly calculates width:100% inside a 13452 * scrolling element (IE6/7) 13453 * @type boolean 13454 * @default false 13455 */ 13456 "bScrollOversize": false, 13457 13458 /** 13459 * Determine if the vertical scrollbar is on the right or left of the 13460 * scrolling container - needed for rtl language layout, although not 13461 * all browsers move the scrollbar (Safari). 13462 * @type boolean 13463 * @default false 13464 */ 13465 "bScrollbarLeft": false, 13466 13467 /** 13468 * Flag for if `getBoundingClientRect` is fully supported or not 13469 * @type boolean 13470 * @default false 13471 */ 13472 "bBounding": false, 13473 13474 /** 13475 * Browser scrollbar width 13476 * @type integer 13477 * @default 0 13478 */ 13479 "barWidth": 0 13480 }, 13481 13482 13483 "ajax": null, 13484 13485 13486 /** 13487 * Array referencing the nodes which are used for the features. The 13488 * parameters of this object match what is allowed by sDom - i.e. 13489 * <ul> 13490 * <li>'l' - Length changing</li> 13491 * <li>'f' - Filtering input</li> 13492 * <li>'t' - The table!</li> 13493 * <li>'i' - Information</li> 13494 * <li>'p' - Pagination</li> 13495 * <li>'r' - pRocessing</li> 13496 * </ul> 13497 * @type array 13498 * @default [] 13499 */ 13500 "aanFeatures": [], 13501 13502 /** 13503 * Store data information - see {@link DataTable.models.oRow} for detailed 13504 * information. 13505 * @type array 13506 * @default [] 13507 */ 13508 "aoData": [], 13509 13510 /** 13511 * Array of indexes which are in the current display (after filtering etc) 13512 * @type array 13513 * @default [] 13514 */ 13515 "aiDisplay": [], 13516 13517 /** 13518 * Array of indexes for display - no filtering 13519 * @type array 13520 * @default [] 13521 */ 13522 "aiDisplayMaster": [], 13523 13524 /** 13525 * Map of row ids to data indexes 13526 * @type object 13527 * @default {} 13528 */ 13529 "aIds": {}, 13530 13531 /** 13532 * Store information about each column that is in use 13533 * @type array 13534 * @default [] 13535 */ 13536 "aoColumns": [], 13537 13538 /** 13539 * Store information about the table's header 13540 * @type array 13541 * @default [] 13542 */ 13543 "aoHeader": [], 13544 13545 /** 13546 * Store information about the table's footer 13547 * @type array 13548 * @default [] 13549 */ 13550 "aoFooter": [], 13551 13552 /** 13553 * Store the applied global search information in case we want to force a 13554 * research or compare the old search to a new one. 13555 * Note that this parameter will be set by the initialisation routine. To 13556 * set a default use {@link DataTable.defaults}. 13557 * @namespace 13558 * @extends DataTable.models.oSearch 13559 */ 13560 "oPreviousSearch": {}, 13561 13562 /** 13563 * Store the applied search for each column - see 13564 * {@link DataTable.models.oSearch} for the format that is used for the 13565 * filtering information for each column. 13566 * @type array 13567 * @default [] 13568 */ 13569 "aoPreSearchCols": [], 13570 13571 /** 13572 * Sorting that is applied to the table. Note that the inner arrays are 13573 * used in the following manner: 13574 * <ul> 13575 * <li>Index 0 - column number</li> 13576 * <li>Index 1 - current sorting direction</li> 13577 * </ul> 13578 * Note that this parameter will be set by the initialisation routine. To 13579 * set a default use {@link DataTable.defaults}. 13580 * @type array 13581 * @todo These inner arrays should really be objects 13582 */ 13583 "aaSorting": null, 13584 13585 /** 13586 * Sorting that is always applied to the table (i.e. prefixed in front of 13587 * aaSorting). 13588 * Note that this parameter will be set by the initialisation routine. To 13589 * set a default use {@link DataTable.defaults}. 13590 * @type array 13591 * @default [] 13592 */ 13593 "aaSortingFixed": [], 13594 13595 /** 13596 * Classes to use for the striping of a table. 13597 * Note that this parameter will be set by the initialisation routine. To 13598 * set a default use {@link DataTable.defaults}. 13599 * @type array 13600 * @default [] 13601 */ 13602 "asStripeClasses": null, 13603 13604 /** 13605 * If restoring a table - we should restore its striping classes as well 13606 * @type array 13607 * @default [] 13608 */ 13609 "asDestroyStripes": [], 13610 13611 /** 13612 * If restoring a table - we should restore its width 13613 * @type int 13614 * @default 0 13615 */ 13616 "sDestroyWidth": 0, 13617 13618 /** 13619 * Callback functions array for every time a row is inserted (i.e. on a draw). 13620 * @type array 13621 * @default [] 13622 */ 13623 "aoRowCallback": [], 13624 13625 /** 13626 * Callback functions for the header on each draw. 13627 * @type array 13628 * @default [] 13629 */ 13630 "aoHeaderCallback": [], 13631 13632 /** 13633 * Callback function for the footer on each draw. 13634 * @type array 13635 * @default [] 13636 */ 13637 "aoFooterCallback": [], 13638 13639 /** 13640 * Array of callback functions for draw callback functions 13641 * @type array 13642 * @default [] 13643 */ 13644 "aoDrawCallback": [], 13645 13646 /** 13647 * Array of callback functions for row created function 13648 * @type array 13649 * @default [] 13650 */ 13651 "aoRowCreatedCallback": [], 13652 13653 /** 13654 * Callback functions for just before the table is redrawn. A return of 13655 * false will be used to cancel the draw. 13656 * @type array 13657 * @default [] 13658 */ 13659 "aoPreDrawCallback": [], 13660 13661 /** 13662 * Callback functions for when the table has been initialised. 13663 * @type array 13664 * @default [] 13665 */ 13666 "aoInitComplete": [], 13667 13668 13669 /** 13670 * Callbacks for modifying the settings to be stored for state saving, prior to 13671 * saving state. 13672 * @type array 13673 * @default [] 13674 */ 13675 "aoStateSaveParams": [], 13676 13677 /** 13678 * Callbacks for modifying the settings that have been stored for state saving 13679 * prior to using the stored values to restore the state. 13680 * @type array 13681 * @default [] 13682 */ 13683 "aoStateLoadParams": [], 13684 13685 /** 13686 * Callbacks for operating on the settings object once the saved state has been 13687 * loaded 13688 * @type array 13689 * @default [] 13690 */ 13691 "aoStateLoaded": [], 13692 13693 /** 13694 * Cache the table ID for quick access 13695 * @type string 13696 * @default <i>Empty string</i> 13697 */ 13698 "sTableId": "", 13699 13700 /** 13701 * The TABLE node for the main table 13702 * @type node 13703 * @default null 13704 */ 13705 "nTable": null, 13706 13707 /** 13708 * Permanent ref to the thead element 13709 * @type node 13710 * @default null 13711 */ 13712 "nTHead": null, 13713 13714 /** 13715 * Permanent ref to the tfoot element - if it exists 13716 * @type node 13717 * @default null 13718 */ 13719 "nTFoot": null, 13720 13721 /** 13722 * Permanent ref to the tbody element 13723 * @type node 13724 * @default null 13725 */ 13726 "nTBody": null, 13727 13728 /** 13729 * Cache the wrapper node (contains all DataTables controlled elements) 13730 * @type node 13731 * @default null 13732 */ 13733 "nTableWrapper": null, 13734 13735 /** 13736 * Indicate if when using server-side processing the loading of data 13737 * should be deferred until the second draw. 13738 * Note that this parameter will be set by the initialisation routine. To 13739 * set a default use {@link DataTable.defaults}. 13740 * @type boolean 13741 * @default false 13742 */ 13743 "bDeferLoading": false, 13744 13745 /** 13746 * Indicate if all required information has been read in 13747 * @type boolean 13748 * @default false 13749 */ 13750 "bInitialised": false, 13751 13752 /** 13753 * Information about open rows. Each object in the array has the parameters 13754 * 'nTr' and 'nParent' 13755 * @type array 13756 * @default [] 13757 */ 13758 "aoOpenRows": [], 13759 13760 /**
13761 * Dictate the positioning of DataTables' control elements - see 13762 * {@link DataTable.model.oInit.sDom}. 13763 * Note that this parameter will be set by the initialisation routine. To 13764 * set a default use {@link DataTable.defaults}. 13765 * @type string 13766 * @default null 13767 */ 13768 "sDom": null, 13769 13770 /** 13771 * Search delay (in mS) 13772 * @type integer 13773 * @default null 13774 */ 13775 "searchDelay": null, 13776 13777 /** 13778 * Which type of pagination should be used. 13779 * Note that this parameter will be set by the initialisation routine. To 13780 * set a default use {@link DataTable.defaults}. 13781 * @type string 13782 * @default two_button 13783 */ 13784 "sPaginationType": "two_button", 13785 13786 /** 13787 * The state duration (for `stateSave`) in seconds. 13788 * Note that this parameter will be set by the initialisation routine. To 13789 * set a default use {@link DataTable.defaults}. 13790 * @type int 13791 * @default 0 13792 */ 13793 "iStateDuration": 0, 13794 13795 /** 13796 * Array of callback functions for state saving. Each array element is an 13797 * object with the following parameters: 13798 * <ul> 13799 * <li>function:fn - function to call. Takes two parameters, oSettings 13800 * and the JSON string to save that has been thus far created. Returns 13801 * a JSON string to be inserted into a json object 13802 * (i.e. '"param": [ 0, 1, 2]')</li> 13803 * <li>string:sName - name of callback</li> 13804 * </ul> 13805 * @type array 13806 * @default [] 13807 */ 13808 "aoStateSave": [], 13809 13810 /** 13811 * Array of callback functions for state loading. Each array element is an 13812 * object with the following parameters: 13813 * <ul> 13814 * <li>function:fn - function to call. Takes two parameters, oSettings 13815 * and the object stored. May return false to cancel state loading</li> 13816 * <li>string:sName - name of callback</li> 13817 * </ul> 13818 * @type array 13819 * @default [] 13820 */ 13821 "aoStateLoad": [], 13822 13823 /** 13824 * State that was saved. Useful for back reference 13825 * @type object 13826 * @default null 13827 */ 13828 "oSavedState": null, 13829 13830 /** 13831 * State that was loaded. Useful for back reference 13832 * @type object 13833 * @default null 13834 */ 13835 "oLoadedState": null, 13836 13837 /** 13838 * Source url for AJAX data for the table. 13839 * Note that this parameter will be set by the initialisation routine. To 13840 * set a default use {@link DataTable.defaults}. 13841 * @type string 13842 * @default null 13843 */ 13844 "sAjaxSource": null, 13845 13846 /** 13847 * Property from a given object from which to read the table data from. This 13848 * can be an empty string (when not server-side processing), in which case 13849 * it is assumed an an array is given directly. 13850 * Note that this parameter will be set by the initialisation routine. To 13851 * set a default use {@link DataTable.defaults}. 13852 * @type string 13853 */ 13854 "sAjaxDataProp": null, 13855 13856 /** 13857 * The last jQuery XHR object that was used for server-side data gathering. 13858 * This can be used for working with the XHR information in one of the 13859 * callbacks 13860 * @type object 13861 * @default null 13862 */ 13863 "jqXHR": null, 13864 13865 /** 13866 * JSON returned from the server in the last Ajax request 13867 * @type object 13868 * @default undefined 13869 */ 13870 "json": undefined, 13871 13872 /** 13873 * Data submitted as part of the last Ajax request 13874 * @type object 13875 * @default undefined 13876 */ 13877 "oAjaxData": undefined, 13878 13879 /** 13880 * Function to get the server-side data. 13881 * Note that this parameter will be set by the initialisation routine. To 13882 * set a default use {@link DataTable.defaults}. 13883 * @type function 13884 */ 13885 "fnServerData": null, 13886 13887 /** 13888 * Functions which are called prior to sending an Ajax request so extra 13889 * parameters can easily be sent to the server 13890 * @type array 13891 * @default [] 13892 */ 13893 "aoServerParams": [], 13894 13895 /** 13896 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if 13897 * required). 13898 * Note that this parameter will be set by the initialisation routine. To 13899 * set a default use {@link DataTable.defaults}. 13900 * @type string 13901 */ 13902 "sServerMethod": null, 13903 13904 /** 13905 * Format numbers for display. 13906 * Note that this parameter will be set by the initialisation routine. To 13907 * set a default use {@link DataTable.defaults}. 13908 * @type function 13909 */ 13910 "fnFormatNumber": null, 13911 13912 /** 13913 * List of options that can be used for the user selectable length menu. 13914 * Note that this parameter will be set by the initialisation routine. To 13915 * set a default use {@link DataTable.defaults}. 13916 * @type array 13917 * @default [] 13918 */ 13919 "aLengthMenu": null, 13920 13921 /** 13922 * Counter for the draws that the table does. Also used as a tracker for 13923 * server-side processing 13924 * @type int 13925 * @default 0 13926 */ 13927 "iDraw": 0, 13928 13929 /** 13930 * Indicate if a redraw is being done - useful for Ajax 13931 * @type boolean 13932 * @default false 13933 */ 13934 "bDrawing": false, 13935 13936 /** 13937 * Draw index (iDraw) of the last error when parsing the returned data 13938 * @type int 13939 * @default -1 13940 */ 13941 "iDrawError": -1, 13942 13943 /** 13944 * Paging display length 13945 * @type int 13946 * @default 10 13947 */ 13948 "_iDisplayLength": 10, 13949 13950 /** 13951 * Paging start point - aiDisplay index 13952 * @type int 13953 * @default 0 13954 */ 13955 "_iDisplayStart": 0, 13956 13957 /** 13958 * Server-side processing - number of records in the result set 13959 * (i.e. before filtering), Use fnRecordsTotal rather than 13960 * this property to get the value of the number of records, regardless of 13961 * the server-side processing setting. 13962 * @type int 13963 * @default 0 13964 * @private 13965 */ 13966 "_iRecordsTotal": 0, 13967 13968 /** 13969 * Server-side processing - number of records in the current display set 13970 * (i.e. after filtering). Use fnRecordsDisplay rather than 13971 * this property to get the value of the number of records, regardless of 13972 * the server-side processing setting. 13973 * @type boolean 13974 * @default 0 13975 * @private 13976 */ 13977 "_iRecordsDisplay": 0, 13978 13979 /** 13980 * The classes to use for the table 13981 * @type object 13982 * @default {} 13983 */
vendor: 4,121 bytes, lines 13984-14155
13984 "oClasses": {}, 13985 13986 /** 13987 * Flag attached to the settings object so you can check in the draw 13988 * callback if filtering has been done in the draw. Deprecated in favour of 13989 * events. 13990 * @type boolean 13991 * @default false 13992 * @deprecated 13993 */ 13994 "bFiltered": false, 13995 13996 /** 13997 * Flag attached to the settings object so you can check in the draw 13998 * callback if sorting has been done in the draw. Deprecated in favour of 13999 * events. 14000 * @type boolean 14001 * @default false 14002 * @deprecated 14003 */ 14004 "bSorted": false, 14005 14006 /** 14007 * Indicate that if multiple rows are in the header and there is more than 14008 * one unique cell per column, if the top one (true) or bottom one (false) 14009 * should be used for sorting / title by DataTables. 14010 * Note that this parameter will be set by the initialisation routine. To 14011 * set a default use {@link DataTable.defaults}. 14012 * @type boolean 14013 */ 14014 "bSortCellsTop": null, 14015 14016 /** 14017 * Initialisation object that is used for the table 14018 * @type object 14019 * @default null 14020 */ 14021 "oInit": null, 14022 14023 /** 14024 * Destroy callback functions - for plug-ins to attach themselves to the 14025 * destroy so they can clean up markup and events. 14026 * @type array 14027 * @default [] 14028 */ 14029 "aoDestroyCallback": [], 14030 14031 14032 /** 14033 * Get the number of records in the current record set, before filtering 14034 * @type function 14035 */ 14036 "fnRecordsTotal": function () 14037 { 14038 return _fnDataSource( this ) == 'ssp' ? 14039 this._iRecordsTotal * 1 : 14040 this.aiDisplayMaster.length; 14041 }, 14042 14043 /** 14044 * Get the number of records in the current record set, after filtering 14045 * @type function 14046 */ 14047 "fnRecordsDisplay": function () 14048 { 14049 return _fnDataSource( this ) == 'ssp' ? 14050 this._iRecordsDisplay * 1 : 14051 this.aiDisplay.length; 14052 }, 14053 14054 /** 14055 * Get the display end point - aiDisplay index 14056 * @type function 14057 */ 14058 "fnDisplayEnd": function () 14059 { 14060 var 14061 len = this._iDisplayLength, 14062 start = this._iDisplayStart, 14063 calc = start + len, 14064 records = this.aiDisplay.length, 14065 features = this.oFeatures, 14066 paginate = features.bPaginate; 14067 14068 if ( features.bServerSide ) { 14069 return paginate === false || len === -1 ? 14070 start + records : 14071 Math.min( start+len, this._iRecordsDisplay ); 14072 } 14073 else { 14074 return ! paginate || calc>records || len===-1 ? 14075 records : 14076 calc; 14077 } 14078 }, 14079 14080 /** 14081 * The DataTables object for this table 14082 * @type object 14083 * @default null 14084 */ 14085 "oInstance": null, 14086 14087 /** 14088 * Unique identifier for each instance of the DataTables object. If there 14089 * is an ID on the table node, then it takes that value, otherwise an 14090 * incrementing internal counter is used. 14091 * @type string 14092 * @default null 14093 */ 14094 "sInstance": null, 14095 14096 /** 14097 * tabindex attribute value that is added to DataTables control elements, allowing 14098 * keyboard navigation of the table and its controls. 14099 */ 14100 "iTabIndex": 0, 14101 14102 /** 14103 * DIV container for the footer scrolling table if scrolling 14104 */ 14105 "nScrollHead": null, 14106 14107 /** 14108 * DIV container for the footer scrolling table if scrolling 14109 */ 14110 "nScrollFoot": null, 14111 14112 /** 14113 * Last applied sort 14114 * @type array 14115 * @default [] 14116 */ 14117 "aLastSort": [], 14118 14119 /** 14120 * Stored plug-in instances 14121 * @type object 14122 * @default {} 14123 */ 14124 "oPlugins": {}, 14125 14126 /** 14127 * Function used to get a row's id from the row's data 14128 * @type function 14129 * @default null 14130 */ 14131 "rowIdFn": null, 14132 14133 /** 14134 * Data location where to store a row's id 14135 * @type string 14136 * @default null 14137 */ 14138 "rowId": null 14139 }; 14140 14141 /** 14142 * Extension object for DataTables that is used to provide all extension 14143 * options. 14144 * 14145 * Note that the `DataTable.ext` object is available through 14146 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is 14147 * also aliased to `jQuery.fn.dataTableExt` for historic reasons. 14148 * @namespace 14149 * @extends DataTable.models.ext 14150 */ 14151 14152 14153 /** 14154 * DataTables extensions 14155 *
14156 * This namespace acts as a collection area for plug-ins that can be used to 14157 * extend DataTables capabilities. Indeed many of the build in methods 14158 * use this method to provide their own capabilities (sorting methods for 14159 * example). 14160 * 14161 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy 14162 * reasons 14163 * 14164 * @namespace 14165 */ 14166 DataTable.ext = _ext = { 14167 /** 14168 * Buttons. For use with the Buttons extension for DataTables. This is 14169 * defined here so other extensions can define buttons regardless of load 14170 * order. It is _not_ used by DataTables core. 14171 * 14172 * @type object 14173 * @default {} 14174 */ 14175 buttons: {}, 14176 14177 14178 /** 14179 * Element class names 14180 * 14181 * @type object 14182 * @default {} 14183 */ 14184 classes: {}, 14185 14186 14187 /** 14188 * DataTables build type (expanded by the download builder) 14189 * 14190 * @type string 14191 */ 14192 build:"dt/dt-1.13.4/af-2.5.3/b-2.3.6/b-colvis-2.3.6/b-html5-2.3.6/b-print-2.3.6/cr-1.6.2/fc-4.2.2/fh-3.3.2/kt-2.9.0/r-2.4.1/rg-1.3.1/rr-1.3.3/sc-2.1.1/sb-1.4.2/sp-2.1.2/sl-1.6.2/sr-1.2.2", 14193 14194 14195 /** 14196 * Error reporting. 14197 * 14198 * How should DataTables report an error. Can take the value 'alert', 14199 * 'throw', 'none' or a function. 14200 * 14201 * @type string|function 14202 * @default alert 14203 */ 14204 errMode: "alert", 14205 14206 14207 /** 14208 * Feature plug-ins. 14209 * 14210 * This is an array of objects which describe the feature plug-ins that are 14211 * available to DataTables. These feature plug-ins are then available for 14212 * use through the `dom` initialisation option. 14213 * 14214 * Each feature plug-in is described by an object which must have the 14215 * following properties: 14216 * 14217 * * `fnInit` - function that is used to initialise the plug-in, 14218 * * `cFeature` - a character so the feature can be enabled by the `dom` 14219 * instillation option. This is case sensitive. 14220 * 14221 * The `fnInit` function has the following input parameters: 14222 * 14223 * 1. `{object}` DataTables settings object: see 14224 * {@link DataTable.models.oSettings} 14225 * 14226 * And the following return is expected: 14227 * 14228 * * {node|null} The element which contains your feature. Note that the 14229 * return may also be void if your plug-in does not require to inject any 14230 * DOM elements into DataTables control (`dom`) - for example this might 14231 * be useful when developing a plug-in which allows table control via 14232 * keyboard entry 14233 * 14234 * @type array 14235 * 14236 * @example 14237 * $.fn.dataTable.ext.features.push( { 14238 * "fnInit": function( oSettings ) { 14239 * return new TableTools( { "oDTSettings": oSettings } ); 14240 * }, 14241 * "cFeature": "T" 14242 * } ); 14243 */ 14244 feature: [], 14245 14246 14247 /** 14248 * Row searching. 14249 * 14250 * This method of searching is complimentary to the default type based 14251 * searching, and a lot more comprehensive as it allows you complete control 14252 * over the searching logic. Each element in this array is a function 14253 * (parameters described below) that is called for every row in the table, 14254 * and your logic decides if it should be included in the searching data set 14255 * or not. 14256 * 14257 * Searching functions have the following input parameters: 14258 * 14259 * 1. `{object}` DataTables settings object: see 14260 * {@link DataTable.models.oSettings} 14261 * 2. `{array|object}` Data for the row to be processed (same as the 14262 * original format that was passed in as the data source, or an array 14263 * from a DOM data source 14264 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which 14265 * can be useful to retrieve the `TR` element if you need DOM interaction. 14266 * 14267 * And the following return is expected: 14268 * 14269 * * {boolean} Include the row in the searched result set (true) or not 14270 * (false) 14271 * 14272 * Note that as with the main search ability in DataTables, technically this 14273 * is "filtering", since it is subtractive. However, for consistency in 14274 * naming we call it searching here. 14275 * 14276 * @type array 14277 * @default [] 14278 * 14279 * @example 14280 * // The following example shows custom search being applied to the 14281 * // fourth column (i.e. the data[3] index) based on two input values 14282 * // from the end-user, matching the data in a certain range. 14283 * $.fn.dataTable.ext.search.push( 14284 * function( settings, data, dataIndex ) { 14285 * var min = document.getElementById('min').value * 1; 14286 * var max = document.getElementById('max').value * 1; 14287 * var version = data[3] == "-" ? 0 : data[3]*1; 14288 * 14289 * if ( min == "" && max == "" ) { 14290 * return true; 14291 * } 14292 * else if ( min == "" && version < max ) { 14293 * return true; 14294 * } 14295 * else if ( min < version && "" == max ) { 14296 * return true; 14297 * } 14298 * else if ( min < version && version < max ) { 14299 * return true; 14300 * } 14301 * return false; 14302 * } 14303 * ); 14304 */ 14305 search: [], 14306 14307 14308 /** 14309 * Selector extensions 14310 * 14311 * The `selector` option can be used to extend the options available for the 14312 * selector modifier options (`selector-modifier` object data type) that 14313 * each of the three built in selector types offer (row, column and cell + 14314 * their plural counterparts). For example the Select extension uses this 14315 * mechanism to provide an option to select only rows, columns and cells 14316 * that have been marked as selected by the end user (`{selected: true}`), 14317 * which can be used in conjunction with the existing built in selector 14318 * options. 14319 * 14320 * Each property is an array to which functions can be pushed. The functions 14321 * take three attributes: 14322 * 14323 * * Settings object for the host table 14324 * * Options object (`selector-modifier` object type) 14325 * * Array of selected item indexes 14326 * 14327 * The return is an array of the resulting item indexes after the custom 14328 * selector has been applied. 14329 * 14330 * @type object 14331 */ 14332 selector: { 14333 cell: [], 14334 column: [], 14335 row: [] 14336 }, 14337 14338 14339 /** 14340 * Internal functions, exposed for used in plug-ins. 14341 * 14342 * Please note that you should not need to use the internal methods for 14343 * anything other than a plug-in (and even then, try to avoid if possible). 14344 * The internal function may change between releases. 14345 * 14346 * @type object 14347 * @default {} 14348 */ 14349 internal: {}, 14350 14351 14352 /** 14353 * Legacy configuration options. Enable and disable legacy options that 14354 * are available in DataTables. 14355 * 14356 * @type object 14357 */ 14358 legacy: { 14359 /** 14360 * Enable / disable DataTables 1.9 compatible server-side processing 14361 * requests 14362 * 14363 * @type boolean 14364 * @default null 14365 */ 14366 ajax: null 14367 }, 14368 14369 14370 /** 14371 * Pagination plug-in methods. 14372 * 14373 * Each entry in this object is a function and defines which buttons should 14374 * be shown by the pagination rendering method that is used for the table: 14375 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the 14376 * buttons are displayed in the document, while the functions here tell it 14377 * what buttons to display. This is done by returning an array of button 14378 * descriptions (what each button will do). 14379 * 14380 * Pagination types (the four built in options and any additional plug-in 14381 * options defined here) can be used through the `paginationType` 14382 * initialisation parameter. 14383 * 14384 * The functions defined take two parameters: 14385 * 14386 * 1. `{int} page` The current page index 14387 * 2. `{int} pages` The number of pages in the table 14388 * 14389 * Each function is expected to return an array where each element of the 14390 * array can be one of: 14391 * 14392 * * `first` - Jump to first page when activated 14393 * * `last` - Jump to last page when activated 14394 * * `previous` - Show previous page when activated 14395 * * `next` - Show next page when activated 14396 * * `{int}` - Show page of the index given 14397 * * `{array}` - A nested array containing the above elements to add a 14398 * containing 'DIV' element (might be useful for styling). 14399 * 14400 * Note that DataTables v1.9- used this object slightly differently whereby 14401 * an object with two functions would be defined for each plug-in. That 14402 * ability is still supported by DataTables 1.10+ to provide backwards 14403 * compatibility, but this option of use is now decremented and no longer 14404 * documented in DataTables 1.10+. 14405 * 14406 * @type object 14407 * @default {} 14408 * 14409 * @example 14410 * // Show previous, next and current page buttons only 14411 * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { 14412 * return [ 'previous', page, 'next' ]; 14413 * }; 14414 */ 14415 pager: {}, 14416 14417 14418 renderer: { 14419 pageButton: {}, 14420 header: {} 14421 }, 14422 14423 14424 /** 14425 * Ordering plug-ins - custom data source 14426 *
14427 * The extension options for ordering of data available here is complimentary 14428 * to the default type based ordering that DataTables typically uses. It 14429 * allows much greater control over the the data that is being used to 14430 * order a column, but is necessarily therefore more complex. 14431 * 14432 * This type of ordering is useful if you want to do ordering based on data 14433 * live from the DOM (for example the contents of an 'input' element) rather 14434 * than just the static string that DataTables knows of. 14435 * 14436 * The way these plug-ins work is that you create an array of the values you 14437 * wish to be ordering for the column in question and then return that 14438 * array. The data in the array much be in the index order of the rows in 14439 * the table (not the currently ordering order!). Which order data gathering 14440 * function is run here depends on the `dt-init columns.orderDataType` 14441 * parameter that is used for the column (if any). 14442 * 14443 * The functions defined take two parameters: 14444 * 14445 * 1. `{object}` DataTables settings object: see 14446 * {@link DataTable.models.oSettings} 14447 * 2. `{int}` Target column index 14448 * 14449 * Each function is expected to return an array: 14450 * 14451 * * `{array}` Data for the column to be ordering upon 14452 * 14453 * @type array 14454 * 14455 * @example 14456 * // Ordering using `input` node values 14457 * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) 14458 * { 14459 * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { 14460 * return $('input', td).val(); 14461 * } ); 14462 * } 14463 */ 14464 order: {}, 14465 14466 14467 /** 14468 * Type based plug-ins. 14469 * 14470 * Each column in DataTables has a type assigned to it, either by automatic 14471 * detection or by direct assignment using the `type` option for the column. 14472 * The type of a column will effect how it is ordering and search (plug-ins 14473 * can also make use of the column type if required). 14474 * 14475 * @namespace 14476 */ 14477 type: { 14478 /** 14479 * Type detection functions. 14480 * 14481 * The functions defined in this object are used to automatically detect 14482 * a column's type, making initialisation of DataTables super easy, even 14483 * when complex data is in the table. 14484 * 14485 * The functions defined take two parameters: 14486 * 14487 * 1. `{*}` Data from the column cell to be analysed 14488 * 2. `{settings}` DataTables settings object. This can be used to 14489 * perform context specific type detection - for example detection 14490 * based on language settings such as using a comma for a decimal 14491 * place. Generally speaking the options from the settings will not 14492 * be required 14493 * 14494 * Each function is expected to return: 14495 * 14496 * * `{string|null}` Data type detected, or null if unknown (and thus 14497 * pass it on to the other type detection functions. 14498 * 14499 * @type array 14500 * 14501 * @example 14502 * // Currency type detection plug-in: 14503 * $.fn.dataTable.ext.type.detect.push( 14504 * function ( data, settings ) { 14505 * // Check the numeric part 14506 * if ( ! data.substring(1).match(/[0-9]/) ) { 14507 * return null; 14508 * } 14509 * 14510 * // Check prefixed by currency 14511 * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { 14512 * return 'currency'; 14513 * } 14514 * return null; 14515 * } 14516 * ); 14517 */ 14518 detect: [], 14519 14520 14521 /** 14522 * Type based search formatting. 14523 * 14524 * The type based searching functions can be used to pre-format the 14525 * data to be search on. For example, it can be used to strip HTML 14526 * tags or to de-format telephone numbers for numeric only searching. 14527 * 14528 * Note that is a search is not defined for a column of a given type, 14529 * no search formatting will be performed. 14530 * 14531 * Pre-processing of searching data plug-ins - When you assign the sType 14532 * for a column (or have it automatically detected for you by DataTables 14533 * or a type detection plug-in), you will typically be using this for 14534 * custom sorting, but it can also be used to provide custom searching 14535 * by allowing you to pre-processing the data and returning the data in 14536 * the format that should be searched upon. This is done by adding 14537 * functions this object with a parameter name which matches the sType 14538 * for that target column. This is the corollary of <i>afnSortData</i> 14539 * for searching data. 14540 * 14541 * The functions defined take a single parameter: 14542 * 14543 * 1. `{*}` Data from the column cell to be prepared for searching 14544 * 14545 * Each function is expected to return: 14546 * 14547 * * `{string|null}` Formatted string that will be used for the searching. 14548 * 14549 * @type object 14550 * @default {} 14551 * 14552 * @example 14553 * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { 14554 * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); 14555 * } 14556 */ 14557 search: {}, 14558 14559 14560 /** 14561 * Type based ordering. 14562 * 14563 * The column type tells DataTables what ordering to apply to the table 14564 * when a column is sorted upon. The order for each type that is defined, 14565 * is defined by the functions available in this object. 14566 * 14567 * Each ordering option can be described by three properties added to 14568 * this object: 14569 * 14570 * * `{type}-pre` - Pre-formatting function 14571 * * `{type}-asc` - Ascending order function 14572 * * `{type}-desc` - Descending order function 14573 * 14574 * All three can be used together, only `{type}-pre` or only 14575 * `{type}-asc` and `{type}-desc` together. It is generally recommended 14576 * that only `{type}-pre` is used, as this provides the optimal 14577 * implementation in terms of speed, although the others are provided 14578 * for compatibility with existing Javascript sort functions. 14579 * 14580 * `{type}-pre`: Functions defined take a single parameter: 14581 * 14582 * 1. `{*}` Data from the column cell to be prepared for ordering 14583 * 14584 * And return: 14585 * 14586 * * `{*}` Data to be sorted upon 14587 * 14588 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort 14589 * functions, taking two parameters: 14590 * 14591 * 1. `{*}` Data to compare to the second parameter 14592 * 2. `{*}` Data to compare to the first parameter 14593 * 14594 * And returning: 14595 * 14596 * * `{*}` Ordering match: <0 if first parameter should be sorted lower 14597 * than the second parameter, ===0 if the two parameters are equal and 14598 * >0 if the first parameter should be sorted height than the second 14599 * parameter. 14600 * 14601 * @type object 14602 * @default {} 14603 * 14604 * @example 14605 * // Numeric ordering of formatted numbers with a pre-formatter 14606 * $.extend( $.fn.dataTable.ext.type.order, { 14607 * "string-pre": function(x) { 14608 * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); 14609 * return parseFloat( a ); 14610 * } 14611 * } ); 14612 * 14613 * @example 14614 * // Case-sensitive string ordering, with no pre-formatting method 14615 * $.extend( $.fn.dataTable.ext.order, { 14616 * "string-case-asc": function(x,y) { 14617 * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 14618 * }, 14619 * "string-case-desc": function(x,y) { 14620 * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); 14621 * } 14622 * } ); 14623 */ 14624 order: {} 14625 }, 14626 14627 /** 14628 * Unique DataTables instance counter 14629 * 14630 * @type int 14631 * @private 14632 */ 14633 _unique: 0, 14634 14635 14636 // 14637 // Depreciated 14638 // The following properties are retained for backwards compatibility only. 14639 // The should not be used in new projects and will be removed in a future 14640 // version 14641 // 14642 14643 /** 14644 * Version check function. 14645 * @type function 14646 * @depreciated Since 1.10 14647 */ 14648 fnVersionCheck: DataTable.fnVersionCheck, 14649 14650 14651 /** 14652 * Index for what 'this' index API functions should use 14653 * @type int 14654 * @deprecated Since v1.10 14655 */ 14656 iApiIndex: 0, 14657 14658 14659 /** 14660 * jQuery UI class container 14661 * @type object 14662 * @deprecated Since v1.10 14663 */ 14664 oJUIClasses: {}, 14665 14666 14667 /** 14668 * Software version 14669 * @type string 14670 * @deprecated Since v1.10 14671 */
vendor: 31,389 bytes, lines 14672-15816
14672 sVersion: DataTable.version 14673 }; 14674 14675 14676 // 14677 // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts 14678 // 14679 $.extend( _ext, { 14680 afnFiltering: _ext.search, 14681 aTypes: _ext.type.detect, 14682 ofnSearch: _ext.type.search, 14683 oSort: _ext.type.order, 14684 afnSortData: _ext.order, 14685 aoFeatures: _ext.feature, 14686 oApi: _ext.internal, 14687 oStdClasses: _ext.classes, 14688 oPagination: _ext.pager 14689 } ); 14690 14691 14692 $.extend( DataTable.ext.classes, { 14693 "sTable": "dataTable", 14694 "sNoFooter": "no-footer", 14695 14696 /* Paging buttons */ 14697 "sPageButton": "paginate_button", 14698 "sPageButtonActive": "current", 14699 "sPageButtonDisabled": "disabled", 14700 14701 /* Striping classes */ 14702 "sStripeOdd": "odd", 14703 "sStripeEven": "even", 14704 14705 /* Empty row */ 14706 "sRowEmpty": "dataTables_empty", 14707 14708 /* Features */ 14709 "sWrapper": "dataTables_wrapper", 14710 "sFilter": "dataTables_filter", 14711 "sInfo": "dataTables_info", 14712 "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ 14713 "sLength": "dataTables_length", 14714 "sProcessing": "dataTables_processing", 14715 14716 /* Sorting */ 14717 "sSortAsc": "sorting_asc", 14718 "sSortDesc": "sorting_desc", 14719 "sSortable": "sorting", /* Sortable in both directions */ 14720 "sSortableAsc": "sorting_desc_disabled", 14721 "sSortableDesc": "sorting_asc_disabled", 14722 "sSortableNone": "sorting_disabled", 14723 "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ 14724 14725 /* Filtering */ 14726 "sFilterInput": "", 14727 14728 /* Page length */ 14729 "sLengthSelect": "", 14730 14731 /* Scrolling */ 14732 "sScrollWrapper": "dataTables_scroll", 14733 "sScrollHead": "dataTables_scrollHead", 14734 "sScrollHeadInner": "dataTables_scrollHeadInner", 14735 "sScrollBody": "dataTables_scrollBody", 14736 "sScrollFoot": "dataTables_scrollFoot", 14737 "sScrollFootInner": "dataTables_scrollFootInner", 14738 14739 /* Misc */ 14740 "sHeaderTH": "", 14741 "sFooterTH": "", 14742 14743 // Deprecated 14744 "sSortJUIAsc": "", 14745 "sSortJUIDesc": "", 14746 "sSortJUI": "", 14747 "sSortJUIAscAllowed": "", 14748 "sSortJUIDescAllowed": "", 14749 "sSortJUIWrapper": "", 14750 "sSortIcon": "", 14751 "sJUIHeader": "", 14752 "sJUIFooter": "" 14753 } ); 14754 14755 14756 var extPagination = DataTable.ext.pager; 14757 14758 function _numbers ( page, pages ) { 14759 var 14760 numbers = [], 14761 buttons = extPagination.numbers_length, 14762 half = Math.floor( buttons / 2 ), 14763 i = 1; 14764 14765 if ( pages <= buttons ) { 14766 numbers = _range( 0, pages ); 14767 } 14768 else if ( page <= half ) { 14769 numbers = _range( 0, buttons-2 ); 14770 numbers.push( 'ellipsis' ); 14771 numbers.push( pages-1 ); 14772 } 14773 else if ( page >= pages - 1 - half ) { 14774 numbers = _range( pages-(buttons-2), pages ); 14775 numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 14776 numbers.splice( 0, 0, 0 ); 14777 } 14778 else { 14779 numbers = _range( page-half+2, page+half-1 ); 14780 numbers.push( 'ellipsis' ); 14781 numbers.push( pages-1 ); 14782 numbers.splice( 0, 0, 'ellipsis' ); 14783 numbers.splice( 0, 0, 0 ); 14784 } 14785 14786 numbers.DT_el = 'span'; 14787 return numbers; 14788 } 14789 14790 14791 $.extend( extPagination, { 14792 simple: function ( page, pages ) { 14793 return [ 'previous', 'next' ]; 14794 }, 14795 14796 full: function ( page, pages ) { 14797 return [ 'first', 'previous', 'next', 'last' ]; 14798 }, 14799 14800 numbers: function ( page, pages ) { 14801 return [ _numbers(page, pages) ]; 14802 }, 14803 14804 simple_numbers: function ( page, pages ) { 14805 return [ 'previous', _numbers(page, pages), 'next' ]; 14806 }, 14807 14808 full_numbers: function ( page, pages ) { 14809 return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; 14810 }, 14811 14812 first_last_numbers: function (page, pages) { 14813 return ['first', _numbers(page, pages), 'last']; 14814 }, 14815 14816 // For testing and plug-ins to use 14817 _numbers: _numbers, 14818 14819 // Number of number buttons (including ellipsis) to show. _Must be odd!_ 14820 numbers_length: 7 14821 } ); 14822 14823 14824 $.extend( true, DataTable.ext.renderer, { 14825 pageButton: { 14826 _: function ( settings, host, idx, buttons, page, pages ) { 14827 var classes = settings.oClasses; 14828 var lang = settings.oLanguage.oPaginate; 14829 var aria = settings.oLanguage.oAria.paginate || {}; 14830 var btnDisplay, btnClass; 14831 14832 var attach = function( container, buttons ) { 14833 var i, ien, node, button, tabIndex; 14834 var disabledClass = classes.sPageButtonDisabled; 14835 var clickHandler = function ( e ) { 14836 _fnPageChange( settings, e.data.action, true ); 14837 }; 14838 14839 for ( i=0, ien=buttons.length ; i<ien ; i++ ) { 14840 button = buttons[i]; 14841 14842 if ( Array.isArray( button ) ) { 14843 var inner = $( '<'+(button.DT_el || 'div')+'/>' ) 14844 .appendTo( container ); 14845 attach( inner, button ); 14846 } 14847 else { 14848 btnDisplay = null; 14849 btnClass = button; 14850 tabIndex = settings.iTabIndex; 14851 14852 switch ( button ) { 14853 case 'ellipsis': 14854 container.append('<span class="ellipsis">…</span>'); 14855 break; 14856 14857 case 'first': 14858 btnDisplay = lang.sFirst; 14859 14860 if ( page === 0 ) { 14861 tabIndex = -1; 14862 btnClass += ' ' + disabledClass; 14863 } 14864 break; 14865 14866 case 'previous': 14867 btnDisplay = lang.sPrevious; 14868 14869 if ( page === 0 ) { 14870 tabIndex = -1; 14871 btnClass += ' ' + disabledClass; 14872 } 14873 break; 14874 14875 case 'next': 14876 btnDisplay = lang.sNext; 14877 14878 if ( pages === 0 || page === pages-1 ) { 14879 tabIndex = -1; 14880 btnClass += ' ' + disabledClass; 14881 } 14882 break; 14883 14884 case 'last': 14885 btnDisplay = lang.sLast; 14886 14887 if ( pages === 0 || page === pages-1 ) { 14888 tabIndex = -1; 14889 btnClass += ' ' + disabledClass; 14890 } 14891 break; 14892 14893 default: 14894 btnDisplay = settings.fnFormatNumber( button + 1 ); 14895 btnClass = page === button ? 14896 classes.sPageButtonActive : ''; 14897 break; 14898 } 14899 14900 if ( btnDisplay !== null ) { 14901 var tag = settings.oInit.pagingTag || 'a'; 14902 var disabled = btnClass.indexOf(disabledClass) !== -1; 14903 14904 14905 node = $('<'+tag+'>', { 14906 'class': classes.sPageButton+' '+btnClass, 14907 'aria-controls': settings.sTableId, 14908 'aria-disabled': disabled ? 'true' : null, 14909 'aria-label': aria[ button ], 14910 'aria-role': 'link', 14911 'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null, 14912 'data-dt-idx': button, 14913 'tabindex': tabIndex, 14914 'id': idx === 0 && typeof button === 'string' ? 14915 settings.sTableId +'_'+ button : 14916 null 14917 } ) 14918 .html( btnDisplay ) 14919 .appendTo( container ); 14920 14921 _fnBindAction( 14922 node, {action: button}, clickHandler 14923 ); 14924 } 14925 } 14926 } 14927 }; 14928 14929 // IE9 throws an 'unknown error' if document.activeElement is used 14930 // inside an iframe or frame. Try / catch the error. Not good for 14931 // accessibility, but neither are frames. 14932 var activeEl; 14933 14934 try { 14935 // Because this approach is destroying and recreating the paging 14936 // elements, focus is lost on the select button which is bad for 14937 // accessibility. So we want to restore focus once the draw has 14938 // completed 14939 activeEl = $(host).find(document.activeElement).data('dt-idx'); 14940 } 14941 catch (e) {} 14942 14943 attach( $(host).empty(), buttons ); 14944 14945 if ( activeEl !== undefined ) { 14946 $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); 14947 } 14948 } 14949 } 14950 } ); 14951 14952 14953 14954 // Built in type detection. See model.ext.aTypes for information about 14955 // what is required from this methods. 14956 $.extend( DataTable.ext.type.detect, [ 14957 // Plain numbers - first since V8 detects some plain numbers as dates 14958 // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). 14959 function ( d, settings ) 14960 { 14961 var decimal = settings.oLanguage.sDecimal; 14962 return _isNumber( d, decimal ) ? 'num'+decimal : null; 14963 }, 14964 14965 // Dates (only those recognised by the browser's Date.parse) 14966 function ( d, settings ) 14967 { 14968 // V8 tries _very_ hard to make a string passed into `Date.parse()` 14969 // valid, so we need to use a regex to restrict date formats. Use a 14970 // plug-in for anything other than ISO8601 style strings 14971 if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { 14972 return null; 14973 } 14974 var parsed = Date.parse(d); 14975 return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; 14976 }, 14977 14978 // Formatted numbers 14979 function ( d, settings ) 14980 { 14981 var decimal = settings.oLanguage.sDecimal; 14982 return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; 14983 }, 14984 14985 // HTML numeric 14986 function ( d, settings ) 14987 { 14988 var decimal = settings.oLanguage.sDecimal; 14989 return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; 14990 }, 14991 14992 // HTML numeric, formatted 14993 function ( d, settings ) 14994 { 14995 var decimal = settings.oLanguage.sDecimal; 14996 return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; 14997 }, 14998 14999 // HTML (this is strict checking - there must be html) 15000 function ( d, settings ) 15001 { 15002 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? 15003 'html' : null; 15004 } 15005 ] ); 15006 15007 15008 15009 // Filter formatting functions. See model.ext.ofnSearch for information about 15010 // what is required from these methods. 15011 // 15012 // Note that additional search methods are added for the html numbers and 15013 // html formatted numbers by `_addNumericSort()` when we know what the decimal 15014 // place is 15015 15016 15017 $.extend( DataTable.ext.type.search, { 15018 html: function ( data ) { 15019 return _empty(data) ? 15020 data : 15021 typeof data === 'string' ? 15022 data 15023 .replace( _re_new_lines, " " ) 15024 .replace( _re_html, "" ) : 15025 ''; 15026 }, 15027 15028 string: function ( data ) { 15029 return _empty(data) ? 15030 data : 15031 typeof data === 'string' ? 15032 data.replace( _re_new_lines, " " ) : 15033 data; 15034 } 15035 } ); 15036 15037 15038 15039 var __numericReplace = function ( d, decimalPlace, re1, re2 ) { 15040 if ( d !== 0 && (!d || d === '-') ) { 15041 return -Infinity; 15042 } 15043 15044 let type = typeof d; 15045 15046 if (type === 'number' || type === 'bigint') { 15047 return d; 15048 } 15049 15050 // If a decimal place other than `.` is used, it needs to be given to the 15051 // function so we can detect it and replace with a `.` which is the only 15052 // decimal place Javascript recognises - it is not locale aware. 15053 if ( decimalPlace ) { 15054 d = _numToDecimal( d, decimalPlace ); 15055 } 15056 15057 if ( d.replace ) { 15058 if ( re1 ) { 15059 d = d.replace( re1, '' ); 15060 } 15061 15062 if ( re2 ) { 15063 d = d.replace( re2, '' ); 15064 } 15065 } 15066 15067 return d * 1; 15068 }; 15069 15070 15071 // Add the numeric 'deformatting' functions for sorting and search. This is done 15072 // in a function to provide an easy ability for the language options to add 15073 // additional methods if a non-period decimal place is used. 15074 function _addNumericSort ( decimalPlace ) { 15075 $.each( 15076 { 15077 // Plain numbers 15078 "num": function ( d ) { 15079 return __numericReplace( d, decimalPlace ); 15080 }, 15081 15082 // Formatted numbers 15083 "num-fmt": function ( d ) { 15084 return __numericReplace( d, decimalPlace, _re_formatted_numeric ); 15085 }, 15086 15087 // HTML numeric 15088 "html-num": function ( d ) { 15089 return __numericReplace( d, decimalPlace, _re_html ); 15090 }, 15091 15092 // HTML numeric, formatted 15093 "html-num-fmt": function ( d ) { 15094 return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); 15095 } 15096 }, 15097 function ( key, fn ) { 15098 // Add the ordering method 15099 _ext.type.order[ key+decimalPlace+'-pre' ] = fn; 15100 15101 // For HTML types add a search formatter that will strip the HTML 15102 if ( key.match(/^html\-/) ) { 15103 _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; 15104 } 15105 } 15106 ); 15107 } 15108 15109 15110 // Default sort methods 15111 $.extend( _ext.type.order, { 15112 // Dates 15113 "date-pre": function ( d ) { 15114 var ts = Date.parse( d ); 15115 return isNaN(ts) ? -Infinity : ts; 15116 }, 15117 15118 // html 15119 "html-pre": function ( a ) { 15120 return _empty(a) ? 15121 '' : 15122 a.replace ? 15123 a.replace( /<.*?>/g, "" ).toLowerCase() : 15124 a+''; 15125 }, 15126 15127 // string 15128 "string-pre": function ( a ) { 15129 // This is a little complex, but faster than always calling toString, 15130 // http://jsperf.com/tostring-v-check 15131 return _empty(a) ? 15132 '' : 15133 typeof a === 'string' ? 15134 a.toLowerCase() : 15135 ! a.toString ? 15136 '' : 15137 a.toString(); 15138 }, 15139 15140 // string-asc and -desc are retained only for compatibility with the old 15141 // sort methods 15142 "string-asc": function ( x, y ) { 15143 return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 15144 }, 15145 15146 "string-desc": function ( x, y ) { 15147 return ((x < y) ? 1 : ((x > y) ? -1 : 0)); 15148 } 15149 } ); 15150 15151 15152 // Numeric sorting types - order doesn't matter here 15153 _addNumericSort( '' ); 15154 15155 15156 $.extend( true, DataTable.ext.renderer, { 15157 header: { 15158 _: function ( settings, cell, column, classes ) { 15159 // No additional mark-up required 15160 // Attach a sort listener to update on sort - note that using the 15161 // `DT` namespace will allow the event to be removed automatically 15162 // on destroy, while the `dt` namespaced event is the one we are 15163 // listening for 15164 $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { 15165 if ( settings !== ctx ) { // need to check this this is the host 15166 return; // table, not a nested one 15167 } 15168 15169 var colIdx = column.idx; 15170 15171 cell 15172 .removeClass( 15173 classes.sSortAsc +' '+ 15174 classes.sSortDesc 15175 ) 15176 .addClass( columns[ colIdx ] == 'asc' ? 15177 classes.sSortAsc : columns[ colIdx ] == 'desc' ? 15178 classes.sSortDesc : 15179 column.sSortingClass 15180 ); 15181 } ); 15182 }, 15183 15184 jqueryui: function ( settings, cell, column, classes ) { 15185 $('<div/>') 15186 .addClass( classes.sSortJUIWrapper ) 15187 .append( cell.contents() ) 15188 .append( $('<span/>') 15189 .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) 15190 ) 15191 .appendTo( cell ); 15192 15193 // Attach a sort listener to update on sort 15194 $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { 15195 if ( settings !== ctx ) { 15196 return; 15197 } 15198 15199 var colIdx = column.idx; 15200 15201 cell 15202 .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) 15203 .addClass( columns[ colIdx ] == 'asc' ? 15204 classes.sSortAsc : columns[ colIdx ] == 'desc' ? 15205 classes.sSortDesc : 15206 column.sSortingClass 15207 ); 15208 15209 cell 15210 .find( 'span.'+classes.sSortIcon ) 15211 .removeClass( 15212 classes.sSortJUIAsc +" "+ 15213 classes.sSortJUIDesc +" "+ 15214 classes.sSortJUI +" "+ 15215 classes.sSortJUIAscAllowed +" "+ 15216 classes.sSortJUIDescAllowed 15217 ) 15218 .addClass( columns[ colIdx ] == 'asc' ? 15219 classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? 15220 classes.sSortJUIDesc : 15221 column.sSortingClassJUI 15222 ); 15223 } ); 15224 } 15225 } 15226 } ); 15227 15228 /* 15229 * Public helper functions. These aren't used internally by DataTables, or 15230 * called by any of the options passed into DataTables, but they can be used 15231 * externally by developers working with DataTables. They are helper functions 15232 * to make working with DataTables a little bit easier. 15233 */ 15234 15235 var __htmlEscapeEntities = function ( d ) { 15236 if (Array.isArray(d)) { 15237 d = d.join(','); 15238 } 15239 15240 return typeof d === 'string' ? 15241 d 15242 .replace(/&/g, '&') 15243 .replace(/</g, '<') 15244 .replace(/>/g, '>') 15245 .replace(/"/g, '"') : 15246 d; 15247 }; 15248 15249 // Common logic for moment, luxon or a date action 15250 function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) { 15251 if (window.moment) { 15252 return dt[momentFn]( arg1 ); 15253 } 15254 else if (window.luxon) { 15255 return dt[luxonFn]( arg1 ); 15256 } 15257 15258 return dateFn ? dt[dateFn]( arg1 ) : dt; 15259 } 15260 15261 15262 var __mlWarning = false; 15263 function __mldObj (d, format, locale) { 15264 var dt; 15265 15266 if (window.moment) { 15267 dt = window.moment.utc( d, format, locale, true ); 15268 15269 if (! dt.isValid()) { 15270 return null; 15271 } 15272 } 15273 else if (window.luxon) { 15274 dt = format && typeof d === 'string' 15275 ? window.luxon.DateTime.fromFormat( d, format ) 15276 : window.luxon.DateTime.fromISO( d ); 15277 15278 if (! dt.isValid) { 15279 return null; 15280 } 15281 15282 dt.setLocale(locale); 15283 } 15284 else if (! format) { 15285 // No format given, must be ISO 15286 dt = new Date(d); 15287 } 15288 else { 15289 if (! __mlWarning) { 15290 alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17'); 15291 } 15292 15293 __mlWarning = true; 15294 } 15295 15296 return dt; 15297 } 15298 15299 // Wrapper for date, datetime and time which all operate the same way with the exception of 15300 // the output string for auto locale support 15301 function __mlHelper (localeString) { 15302 return function ( from, to, locale, def ) { 15303 // Luxon and Moment support 15304 // Argument shifting 15305 if ( arguments.length === 0 ) { 15306 locale = 'en'; 15307 to = null; // means toLocaleString 15308 from = null; // means iso8601 15309 } 15310 else if ( arguments.length === 1 ) { 15311 locale = 'en'; 15312 to = from; 15313 from = null; 15314 } 15315 else if ( arguments.length === 2 ) { 15316 locale = to; 15317 to = from; 15318 from = null; 15319 } 15320 15321 var typeName = 'datetime-' + to; 15322 15323 // Add type detection and sorting specific to this date format - we need to be able to identify 15324 // date type columns as such, rather than as numbers in extensions. Hence the need for this. 15325 if (! DataTable.ext.type.order[typeName]) { 15326 // The renderer will give the value to type detect as the type! 15327 DataTable.ext.type.detect.unshift(function (d) { 15328 return d === typeName ? typeName : false; 15329 }); 15330 15331 // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a 15332 // `valueOf` which gives milliseconds epoch 15333 DataTable.ext.type.order[typeName + '-asc'] = function (a, b) { 15334 var x = a.valueOf(); 15335 var y = b.valueOf(); 15336 15337 return x === y 15338 ? 0 15339 : x < y 15340 ? -1 15341 : 1; 15342 } 15343 15344 DataTable.ext.type.order[typeName + '-desc'] = function (a, b) { 15345 var x = a.valueOf(); 15346 var y = b.valueOf(); 15347 15348 return x === y 15349 ? 0 15350 : x > y 15351 ? -1 15352 : 1; 15353 } 15354 } 15355 15356 return function ( d, type ) { 15357 // Allow for a default value 15358 if (d === null || d === undefined) { 15359 if (def === '--now') { 15360 // We treat everything as UTC further down, so no changes are 15361 // made, as such need to get the local date / time as if it were 15362 // UTC 15363 var local = new Date(); 15364 d = new Date( Date.UTC( 15365 local.getFullYear(), local.getMonth(), local.getDate(), 15366 local.getHours(), local.getMinutes(), local.getSeconds() 15367 ) ); 15368 } 15369 else { 15370 d = ''; 15371 } 15372 } 15373 15374 if (type === 'type') { 15375 // Typing uses the type name for fast matching 15376 return typeName; 15377 } 15378 15379 if (d === '') { 15380 return type !== 'sort' 15381 ? '' 15382 : __mldObj('0000-01-01 00:00:00', null, locale); 15383 } 15384 15385 // Shortcut. If `from` and `to` are the same, we are using the renderer to 15386 // format for ordering, not display - its already in the display format. 15387 if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) { 15388 return d; 15389 } 15390 15391 var dt = __mldObj(d, from, locale); 15392 15393 if (dt === null) { 15394 return d; 15395 } 15396 15397 if (type === 'sort') { 15398 return dt; 15399 } 15400 15401 var formatted = to === null 15402 ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]() 15403 : __mld(dt, 'format', 'toFormat', 'toISOString', to); 15404 15405 // XSS protection 15406 return type === 'display' ? 15407 __htmlEscapeEntities( formatted ) : 15408 formatted; 15409 }; 15410 } 15411 } 15412 15413 // Based on locale, determine standard number formatting 15414 // Fallback for legacy browsers is US English 15415 var __thousands = ','; 15416 var __decimal = '.'; 15417 15418 if (Intl) { 15419 try { 15420 var num = new Intl.NumberFormat().formatToParts(100000.1); 15421 15422 for (var i=0 ; i<num.length ; i++) { 15423 if (num[i].type === 'group') { 15424 __thousands = num[i].value; 15425 } 15426 else if (num[i].type === 'decimal') { 15427 __decimal = num[i].value; 15428 } 15429 } 15430 } 15431 catch (e) { 15432 // noop 15433 } 15434 } 15435 15436 // Formatted date time detection - use by declaring the formats you are going to use 15437 DataTable.datetime = function ( format, locale ) { 15438 var typeName = 'datetime-detect-' + format; 15439 15440 if (! locale) { 15441 locale = 'en'; 15442 } 15443 15444 if (! DataTable.ext.type.order[typeName]) { 15445 DataTable.ext.type.detect.unshift(function (d) { 15446 var dt = __mldObj(d, format, locale); 15447 return d === '' || dt ? typeName : false; 15448 }); 15449 15450 DataTable.ext.type.order[typeName + '-pre'] = function (d) { 15451 return __mldObj(d, format, locale) || 0; 15452 } 15453 } 15454 } 15455 15456 /** 15457 * Helpers for `columns.render`. 15458 * 15459 * The options defined here can be used with the `columns.render` initialisation 15460 * option to provide a display renderer. The following functions are defined: 15461 * 15462 * * `number` - Will format numeric data (defined by `columns.data`) for 15463 * display, retaining the original unformatted data for sorting and filtering. 15464 * It takes 5 parameters: 15465 * * `string` - Thousands grouping separator 15466 * * `string` - Decimal point indicator 15467 * * `integer` - Number of decimal points to show 15468 * * `string` (optional) - Prefix. 15469 * * `string` (optional) - Postfix (/suffix). 15470 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional 15471 * parameters. 15472 * 15473 * @example 15474 * // Column definition using the number renderer 15475 * { 15476 * data: "salary", 15477 * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) 15478 * } 15479 * 15480 * @namespace 15481 */ 15482 DataTable.render = { 15483 date: __mlHelper('toLocaleDateString'), 15484 datetime: __mlHelper('toLocaleString'), 15485 time: __mlHelper('toLocaleTimeString'), 15486 number: function ( thousands, decimal, precision, prefix, postfix ) { 15487 // Auto locale detection 15488 if (thousands === null || thousands === undefined) { 15489 thousands = __thousands; 15490 } 15491 15492 if (decimal === null || decimal === undefined) { 15493 decimal = __decimal; 15494 } 15495 15496 return { 15497 display: function ( d ) { 15498 if ( typeof d !== 'number' && typeof d !== 'string' ) { 15499 return d; 15500 } 15501 15502 if (d === '' || d === null) { 15503 return d; 15504 } 15505 15506 var negative = d < 0 ? '-' : ''; 15507 var flo = parseFloat( d ); 15508 15509 // If NaN then there isn't much formatting that we can do - just 15510 // return immediately, escaping any HTML (this was supposed to 15511 // be a number after all) 15512 if ( isNaN( flo ) ) { 15513 return __htmlEscapeEntities( d ); 15514 } 15515 15516 flo = flo.toFixed( precision ); 15517 d = Math.abs( flo ); 15518 15519 var intPart = parseInt( d, 10 ); 15520 var floatPart = precision ? 15521 decimal+(d - intPart).toFixed( precision ).substring( 2 ): 15522 ''; 15523 15524 // If zero, then can't have a negative prefix 15525 if (intPart === 0 && parseFloat(floatPart) === 0) { 15526 negative = ''; 15527 } 15528 15529 return negative + (prefix||'') + 15530 intPart.toString().replace( 15531 /\B(?=(\d{3})+(?!\d))/g, thousands 15532 ) + 15533 floatPart + 15534 (postfix||''); 15535 } 15536 }; 15537 }, 15538 15539 text: function () { 15540 return { 15541 display: __htmlEscapeEntities, 15542 filter: __htmlEscapeEntities 15543 }; 15544 } 15545 }; 15546 15547 15548 /* 15549 * This is really a good bit rubbish this method of exposing the internal methods 15550 * publicly... - To be fixed in 2.0 using methods on the prototype 15551 */ 15552 15553 15554 /** 15555 * Create a wrapper function for exporting an internal functions to an external API. 15556 * @param {string} fn API function name 15557 * @returns {function} wrapped function 15558 * @memberof DataTable#internal 15559 */ 15560 function _fnExternApiFunc (fn) 15561 { 15562 return function() { 15563 var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( 15564 Array.prototype.slice.call(arguments) 15565 ); 15566 return DataTable.ext.internal[fn].apply( this, args ); 15567 }; 15568 } 15569 15570 15571 /** 15572 * Reference to internal functions for use by plug-in developers. Note that 15573 * these methods are references to internal functions and are considered to be 15574 * private. If you use these methods, be aware that they are liable to change 15575 * between versions. 15576 * @namespace 15577 */ 15578 $.extend( DataTable.ext.internal, { 15579 _fnExternApiFunc: _fnExternApiFunc, 15580 _fnBuildAjax: _fnBuildAjax, 15581 _fnAjaxUpdate: _fnAjaxUpdate, 15582 _fnAjaxParameters: _fnAjaxParameters, 15583 _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, 15584 _fnAjaxDataSrc: _fnAjaxDataSrc, 15585 _fnAddColumn: _fnAddColumn, 15586 _fnColumnOptions: _fnColumnOptions, 15587 _fnAdjustColumnSizing: _fnAdjustColumnSizing, 15588 _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, 15589 _fnColumnIndexToVisible: _fnColumnIndexToVisible, 15590 _fnVisbleColumns: _fnVisbleColumns, 15591 _fnGetColumns: _fnGetColumns, 15592 _fnColumnTypes: _fnColumnTypes, 15593 _fnApplyColumnDefs: _fnApplyColumnDefs, 15594 _fnHungarianMap: _fnHungarianMap, 15595 _fnCamelToHungarian: _fnCamelToHungarian, 15596 _fnLanguageCompat: _fnLanguageCompat, 15597 _fnBrowserDetect: _fnBrowserDetect, 15598 _fnAddData: _fnAddData, 15599 _fnAddTr: _fnAddTr, 15600 _fnNodeToDataIndex: _fnNodeToDataIndex, 15601 _fnNodeToColumnIndex: _fnNodeToColumnIndex, 15602 _fnGetCellData: _fnGetCellData, 15603 _fnSetCellData: _fnSetCellData, 15604 _fnSplitObjNotation: _fnSplitObjNotation, 15605 _fnGetObjectDataFn: _fnGetObjectDataFn, 15606 _fnSetObjectDataFn: _fnSetObjectDataFn, 15607 _fnGetDataMaster: _fnGetDataMaster, 15608 _fnClearTable: _fnClearTable, 15609 _fnDeleteIndex: _fnDeleteIndex, 15610 _fnInvalidate: _fnInvalidate, 15611 _fnGetRowElements: _fnGetRowElements, 15612 _fnCreateTr: _fnCreateTr, 15613 _fnBuildHead: _fnBuildHead, 15614 _fnDrawHead: _fnDrawHead, 15615 _fnDraw: _fnDraw, 15616 _fnReDraw: _fnReDraw, 15617 _fnAddOptionsHtml: _fnAddOptionsHtml, 15618 _fnDetectHeader: _fnDetectHeader, 15619 _fnGetUniqueThs: _fnGetUniqueThs, 15620 _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, 15621 _fnFilterComplete: _fnFilterComplete, 15622 _fnFilterCustom: _fnFilterCustom, 15623 _fnFilterColumn: _fnFilterColumn, 15624 _fnFilter: _fnFilter, 15625 _fnFilterCreateSearch: _fnFilterCreateSearch, 15626 _fnEscapeRegex: _fnEscapeRegex, 15627 _fnFilterData: _fnFilterData, 15628 _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, 15629 _fnUpdateInfo: _fnUpdateInfo, 15630 _fnInfoMacros: _fnInfoMacros, 15631 _fnInitialise: _fnInitialise, 15632 _fnInitComplete: _fnInitComplete, 15633 _fnLengthChange: _fnLengthChange, 15634 _fnFeatureHtmlLength: _fnFeatureHtmlLength, 15635 _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, 15636 _fnPageChange: _fnPageChange, 15637 _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, 15638 _fnProcessingDisplay: _fnProcessingDisplay, 15639 _fnFeatureHtmlTable: _fnFeatureHtmlTable, 15640 _fnScrollDraw: _fnScrollDraw, 15641 _fnApplyToChildren: _fnApplyToChildren, 15642 _fnCalculateColumnWidths: _fnCalculateColumnWidths, 15643 _fnThrottle: _fnThrottle, 15644 _fnConvertToWidth: _fnConvertToWidth, 15645 _fnGetWidestNode: _fnGetWidestNode, 15646 _fnGetMaxLenString: _fnGetMaxLenString, 15647 _fnStringToCss: _fnStringToCss, 15648 _fnSortFlatten: _fnSortFlatten, 15649 _fnSort: _fnSort, 15650 _fnSortAria: _fnSortAria, 15651 _fnSortListener: _fnSortListener, 15652 _fnSortAttachListener: _fnSortAttachListener, 15653 _fnSortingClasses: _fnSortingClasses, 15654 _fnSortData: _fnSortData, 15655 _fnSaveState: _fnSaveState, 15656 _fnLoadState: _fnLoadState, 15657 _fnImplementState: _fnImplementState, 15658 _fnSettingsFromNode: _fnSettingsFromNode, 15659 _fnLog: _fnLog, 15660 _fnMap: _fnMap, 15661 _fnBindAction: _fnBindAction, 15662 _fnCallbackReg: _fnCallbackReg, 15663 _fnCallbackFire: _fnCallbackFire, 15664 _fnLengthOverflow: _fnLengthOverflow, 15665 _fnRenderer: _fnRenderer, 15666 _fnDataSource: _fnDataSource, 15667 _fnRowAttributes: _fnRowAttributes, 15668 _fnExtend: _fnExtend, 15669 _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant 15670 // in 1.10, so this dead-end function is 15671 // added to prevent errors 15672 } ); 15673 15674 15675 // jQuery access 15676 $.fn.dataTable = DataTable; 15677 15678 // Provide access to the host jQuery object (circular reference) 15679 DataTable.$ = $; 15680 15681 // Legacy aliases 15682 $.fn.dataTableSettings = DataTable.settings; 15683 $.fn.dataTableExt = DataTable.ext; 15684 15685 // With a capital `D` we return a DataTables API instance rather than a 15686 // jQuery object 15687 $.fn.DataTable = function ( opts ) { 15688 return $(this).dataTable( opts ).api(); 15689 }; 15690 15691 // All properties that are available to $.fn.dataTable should also be 15692 // available on $.fn.DataTable 15693 $.each( DataTable, function ( prop, val ) { 15694 $.fn.DataTable[ prop ] = val; 15695 } ); 15696 15697 return DataTable; 15698})); 15699 15700 15701/*! DataTables styling integration 15702 * ©2018 SpryMedia Ltd - datatables.net/license 15703 */ 15704 15705(function( factory ){ 15706 if ( typeof define === 'function' && define.amd ) { 15707 // AMD 15708 define( ['jquery', 'datatables.net'], function ( $ ) { 15709 return factory( $, window, document ); 15710 } ); 15711 } 15712 else if ( typeof exports === 'object' ) { 15713 // CommonJS 15714 var jq = require('jquery'); 15715 var cjsRequires = function (root, $) { 15716 if ( ! $.fn.dataTable ) { 15717 require('datatables.net')(root, $); 15718 } 15719 }; 15720 15721 if (typeof window !== 'undefined') { 15722 module.exports = function (root, $) { 15723 if ( ! root ) { 15724 // CommonJS environments without a window global must pass a 15725 // root. This will give an error otherwise 15726 root = window; 15727 } 15728 15729 if ( ! $ ) { 15730 $ = jq( root ); 15731 } 15732 15733 cjsRequires( root, $ ); 15734 return factory( $, root, root.document ); 15735 }; 15736 } 15737 else { 15738 cjsRequires( window, jq ); 15739 module.exports = factory( jq, window, window.document ); 15740 } 15741 } 15742 else { 15743 // Browser 15744 factory( jQuery, window, document ); 15745 } 15746}(function( $, window, document, undefined ) { 15747'use strict'; 15748var DataTable = $.fn.dataTable; 15749 15750 15751 15752 15753 15754return DataTable; 15755})); 15756 15757 15758/*! AutoFill 2.5.3 15759 * ©2008-2023 SpryMedia Ltd - datatables.net/license 15760 */ 15761 15762(function( factory ){ 15763 if ( typeof define === 'function' && define.amd ) { 15764 // AMD 15765 define( ['jquery', 'datatables.net'], function ( $ ) { 15766 return factory( $, window, document ); 15767 } ); 15768 } 15769 else if ( typeof exports === 'object' ) { 15770 // CommonJS 15771 var jq = require('jquery'); 15772 var cjsRequires = function (root, $) { 15773 if ( ! $.fn.dataTable ) { 15774 require('datatables.net')(root, $); 15775 } 15776 }; 15777 15778 if (typeof window !== 'undefined') { 15779 module.exports = function (root, $) { 15780 if ( ! root ) { 15781 // CommonJS environments without a window global must pass a 15782 // root. This will give an error otherwise 15783 root = window; 15784 } 15785 15786 if ( ! $ ) { 15787 $ = jq( root ); 15788 } 15789 15790 cjsRequires( root, $ ); 15791 return factory( $, root, root.document ); 15792 }; 15793 } 15794 else { 15795 cjsRequires( window, jq ); 15796 module.exports = factory( jq, window, window.document ); 15797 } 15798 } 15799 else { 15800 // Browser 15801 factory( jQuery, window, document ); 15802 } 15803}(function( $, window, document, undefined ) { 15804'use strict'; 15805var DataTable = $.fn.dataTable; 15806 15807 15808 15809/** 15810 * @summary AutoFill 15811 * @description Add Excel like click and drag auto-fill options to DataTables 15812 * @version 2.5.3 15813 * @author SpryMedia Ltd (www.sprymedia.co.uk) 15814 * @copyright SpryMedia Ltd. 15815 * 15816 * This source file is free software, available under the following license:
15817 * MIT license - http://datatables.net/license/mit 15818 * 15819 * This source file is distributed in the hope that it will be useful, but 15820 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 15821 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 15822 * 15823 * For details please refer to: http://www.datatables.net 15824 */ 15825 15826var _instance = 0; 15827 15828/** 15829 * AutoFill provides Excel like auto-fill features for a DataTable 15830 * 15831 * @class AutoFill 15832 * @constructor 15833 * @param {object} oTD DataTables settings object 15834 * @param {object} oConfig Configuration object for AutoFill 15835 */ 15836var AutoFill = function( dt, opts ) 15837{ 15838 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { 15839 throw( "Warning: AutoFill requires DataTables 1.10.8 or greater"); 15840 } 15841 15842 // User and defaults configuration object 15843 this.c = $.extend( true, {}, 15844 DataTable.defaults.autoFill, 15845 AutoFill.defaults, 15846 opts 15847 ); 15848 15849 /** 15850 * @namespace Settings object which contains customisable information for AutoFill instance 15851 */ 15852 this.s = { 15853 /** @type {DataTable.Api} DataTables' API instance */ 15854 dt: new DataTable.Api( dt ), 15855 15856 /** @type {String} Unique namespace for events attached to the document */ 15857 namespace: '.autoFill'+(_instance++), 15858 15859 /** @type {Object} Cached dimension information for use in the mouse move event handler */ 15860 scroll: {}, 15861 15862 /** @type {integer} Interval object used for smooth scrolling */ 15863 scrollInterval: null, 15864 15865 handle: { 15866 height: 0, 15867 width: 0 15868 }, 15869 15870 /** 15871 * Enabled setting 15872 * @type {Boolean} 15873 */ 15874 enabled: false 15875 }; 15876 15877 15878 /** 15879 * @namespace Common and useful DOM elements for the class instance 15880 */ 15881 this.dom = { 15882 closeButton: $('<div class="dtaf-popover-close">x</div>'), 15883 15884 /** @type {jQuery} AutoFill handle */ 15885 handle: $('<div class="dt-autofill-handle"/>'), 15886 15887 /** 15888 * @type {Object} Selected cells outline - Need to use 4 elements, 15889 * otherwise the mouse over if you back into the selected rectangle 15890 * will be over that element, rather than the cells! 15891 */ 15892 select: { 15893 top: $('<div class="dt-autofill-select top"/>'), 15894 right: $('<div class="dt-autofill-select right"/>'), 15895 bottom: $('<div class="dt-autofill-select bottom"/>'), 15896 left: $('<div class="dt-autofill-select left"/>') 15897 }, 15898 15899 /** @type {jQuery} Fill type chooser background */ 15900 background: $('<div class="dt-autofill-background"/>'), 15901 15902 /** @type {jQuery} Fill type chooser */ 15903 list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'), 15904 15905 /** @type {jQuery} DataTables scrolling container */ 15906 dtScroll: null, 15907 15908 /** @type {jQuery} Offset parent element */ 15909 offsetParent: null 15910 }; 15911 15912 15913 /* Constructor logic */ 15914 this._constructor(); 15915}; 15916 15917 15918 15919$.extend( AutoFill.prototype, { 15920 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 15921 * Public methods (exposed via the DataTables API below) 15922 */ 15923 enabled: function () 15924 { 15925 return this.s.enabled; 15926 }, 15927 15928 15929 enable: function ( flag ) 15930 { 15931 var that = this; 15932 15933 if ( flag === false ) { 15934 return this.disable(); 15935 } 15936 15937 this.s.enabled = true; 15938 15939 this._focusListener(); 15940 15941 this.dom.handle.on( 'mousedown touchstart', function (e) { 15942 that._mousedown( e ); 15943 return false; 15944 } ); 15945 15946 $(window).on('resize', function() { 15947 var handle = $('div.dt-autofill-handle'); 15948 if(handle.length > 0 && that.dom.attachedTo !== undefined) { 15949 that._attach(that.dom.attachedTo) 15950 } 15951 }) 15952 15953 let orientationReset = function() { 15954 that.s.handle = { 15955 height: false, 15956 width: false 15957 }; 15958 $(that.dom.handle).css({ 15959 'height': '', 15960 'width': '' 15961 }) 15962 if(that.dom.attachedTo !== undefined) { 15963 that._attach(that.dom.attachedTo) 15964 } 15965 } 15966 15967 $(window) 15968 .on('orientationchange', function() { 15969 setTimeout(function() { 15970 orientationReset(); 15971 setTimeout(orientationReset, 150); 15972 }, 50); 15973 }); 15974 15975 return this; 15976 }, 15977 15978 disable: function () 15979 { 15980 this.s.enabled = false; 15981 15982 this._focusListenerRemove(); 15983 15984 return this; 15985 }, 15986 15987 15988 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 15989 * Constructor 15990 */ 15991 15992 /** 15993 * Initialise the RowReorder instance 15994 * 15995 * @private 15996 */ 15997 _constructor: function () 15998 { 15999 var that = this; 16000 var dt = this.s.dt; 16001 var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container()); 16002 16003 // Make the instance accessible to the API 16004 dt.settings()[0].autoFill = this; 16005 16006 if ( dtScroll.length ) { 16007 this.dom.dtScroll = dtScroll; 16008 16009 // Need to scroll container to be the offset parent 16010 if ( dtScroll.css('position') === 'static' ) { 16011 dtScroll.css( 'position', 'relative' ); 16012 } 16013 } 16014 16015 if ( this.c.enable !== false ) { 16016 this.enable(); 16017 } 16018 16019 dt.on( 'destroy.autoFill', function () { 16020 that._focusListenerRemove(); 16021 } ); 16022 }, 16023 16024 16025 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16026 * Private methods 16027 */ 16028 16029 /** 16030 * Display the AutoFill drag handle by appending it to a table cell. This 16031 * is the opposite of the _detach method. 16032 * 16033 * @param {node} node TD/TH cell to insert the handle into 16034 * @private 16035 */ 16036 _attach: function ( node ) 16037 { 16038 var dt = this.s.dt; 16039 var idx = dt.cell( node ).index(); 16040 var handle = this.dom.handle; 16041 var handleDim = this.s.handle; 16042 16043 if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) { 16044 this._detach(); 16045 return; 16046 } 16047 16048 if ( ! this.dom.offsetParent ) { 16049 // We attach to the table's offset parent 16050 this.dom.offsetParent = $( dt.table().node() ).offsetParent(); 16051 } 16052 16053 if ( ! handleDim.height || ! handleDim.width ) { 16054 // Append to document so we can get its size. Not expecting it to 16055 // change during the life time of the page 16056 handle.appendTo( 'body' ); 16057 handleDim.height = handle.outerHeight(); 16058 handleDim.width = handle.outerWidth(); 16059 } 16060 16061 // Might need to go through multiple offset parents 16062 var offset = this._getPosition( node, this.dom.offsetParent ); 16063 16064 this.dom.attachedTo = node; 16065 handle 16066 .css( { 16067 top: offset.top + node.offsetHeight - handleDim.height, 16068 left: offset.left + node.offsetWidth - handleDim.width 16069 } ) 16070 .appendTo( this.dom.offsetParent ); 16071 }, 16072 16073 16074 /** 16075 * Determine can the fill type should be. This can be automatic, or ask the 16076 * end user. 16077 * 16078 * @param {array} cells Information about the selected cells from the key 16079 * up function 16080 * @private 16081 */ 16082 _actionSelector: function ( cells ) 16083 { 16084 var that = this; 16085 var dt = this.s.dt; 16086 var actions = AutoFill.actions; 16087 var available = []; 16088 16089 // "Ask" each plug-in if it wants to handle this data 16090 $.each( actions, function ( key, action ) { 16091 if ( action.available( dt, cells ) ) { 16092 available.push( key ); 16093 } 16094 } ); 16095 16096 if ( available.length === 1 && this.c.alwaysAsk === false ) { 16097 // Only one action available - enact it immediately 16098 var result = actions[ available[0] ].execute( dt, cells ); 16099 this._update( result, cells ); 16100 } 16101 else if ( available.length > 1 ) { 16102 // Multiple actions available - ask the end user what they want to do 16103 var list = this.dom.list.children('ul').empty(); 16104 16105 // Add a cancel option 16106 available.push( 'cancel' ); 16107 16108 $.each( available, function ( i, name ) { 16109 list.append( $('<li/>') 16110 .append( 16111 '<div class="dt-autofill-question">'+ 16112 actions[ name ].option( dt, cells )+ 16113 '<div>' 16114 ) 16115 .append( $('<div class="dt-autofill-button">' ).append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '>')+'</button>'))) 16116 .on( 'click', function () { 16117 var result = actions[ name ].execute( 16118 dt, cells, $(this).closest('li') 16119 ); 16120 that._update( result, cells ); 16121 16122 that.dom.background.remove(); 16123 that.dom.list.remove(); 16124 } ) 16125 ); 16126 } ); 16127 16128 this.dom.background.appendTo( 'body' ); 16129 this.dom.background.one('click', function() { 16130 that.dom.background.remove(); 16131 that.dom.list.remove(); 16132 }) 16133 this.dom.list.appendTo( 'body' ); 16134 16135 if (this.c.closeButton) { 16136 this.dom.list.prepend(this.dom.closeButton).addClass(AutoFill.classes.closeable) 16137 this.dom.closeButton.on('click', function() { 16138 return that.dom.background.click() 16139 }); 16140 } 16141 16142 this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 ); 16143 } 16144 }, 16145 16146 16147 /** 16148 * Remove the AutoFill handle from the document 16149 * 16150 * @private 16151 */ 16152 _detach: function () 16153 { 16154 this.dom.attachedTo = null; 16155 this.dom.handle.detach(); 16156 }, 16157 16158 16159 /** 16160 * Draw the selection outline by calculating the range between the start 16161 * and end cells, then placing the highlighting elements to draw a rectangle 16162 * 16163 * @param {node} target End cell 16164 * @param {object} e Originating event 16165 * @private 16166 */ 16167 _drawSelection: function ( target, e ) 16168 { 16169 // Calculate boundary for start cell to this one 16170 var dt = this.s.dt; 16171 var start = this.s.start; 16172 var startCell = $(this.dom.start); 16173 var end = { 16174 row: this.c.vertical ? 16175 dt.rows( { page: 'current' } ).nodes().indexOf( target.parentNode ) : 16176 start.row, 16177 column: this.c.horizontal ? 16178 $(target).index() : 16179 start.column 16180 }; 16181 var colIndx = dt.column.index( 'toData', end.column ); 16182 var endRow = dt.row( ':eq('+end.row+')', { page: 'current' } ); // Workaround for M581 16183 var endCell = $( dt.cell( endRow.index(), colIndx ).node() ); 16184 16185 // Be sure that is a DataTables controlled cell 16186 if ( ! dt.cell( endCell ).any() ) { 16187 return; 16188 } 16189 16190 // if target is not in the columns available - do nothing 16191 if ( dt.columns( this.c.columns ).indexes().indexOf( colIndx ) === -1 || end.row
16191=== -1) { 16192 return; 16193 } 16194 16195 this.s.end = end; 16196 16197 var top, bottom, left, right, height, width; 16198 16199 top = start.row < end.row ? startCell : endCell; 16200 bottom = start.row < end.row ? endCell : startCell; 16201 left = start.column < end.column ? startCell : endCell; 16202 right = start.column < end.column ? endCell : startCell; 16203 16204 top = this._getPosition( top.get(0) ).top; 16205 left = this._getPosition( left.get(0) ).left; 16206 height = this._getPosition( bottom.get(0) ).top + bottom.outerHeight() - top; 16207 width = this._getPosition( right.get(0) ).left + right.outerWidth() - left; 16208 16209 var select = this.dom.select; 16210 select.top.css( { 16211 top: top, 16212 left: left, 16213 width: width 16214 } ); 16215 16216 select.left.css( { 16217 top: top, 16218 left: left, 16219 height: height 16220 } ); 16221 16222 select.bottom.css( { 16223 top: top + height, 16224 left: left, 16225 width: width 16226 } ); 16227 16228 select.right.css( { 16229 top: top, 16230 left: left + width, 16231 height: height 16232 } ); 16233 }, 16234 16235 16236 /** 16237 * Use the Editor API to perform an update based on the new data for the 16238 * cells 16239 * 16240 * @param {array} cells Information about the selected cells from the key 16241 * up function 16242 * @private 16243 */ 16244 _editor: function ( cells ) 16245 { 16246 var dt = this.s.dt; 16247 var editor = this.c.editor; 16248 16249 if ( ! editor ) { 16250 return; 16251 } 16252 16253 // Build the object structure for Editor's multi-row editing 16254 var idValues = {}; 16255 var nodes = []; 16256 var fields = editor.fields(); 16257 16258 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16259 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16260 var cell = cells[i][j]; 16261 16262 // Determine the field name for the cell being edited 16263 var col = dt.settings()[0].aoColumns[ cell.index.column ]; 16264 var fieldName = col.editField; 16265 16266 if ( fieldName === undefined ) { 16267 var dataSrc = col.mData; 16268 16269 // dataSrc is the `field.data` property, but we need to set 16270 // using the field name, so we need to translate from the 16271 // data to the name 16272 for ( var k=0, ken=fields.length ; k<ken ; k++ ) { 16273 var field = editor.field( fields[k] ); 16274 16275 if ( field.dataSrc() === dataSrc ) { 16276 fieldName = field.name(); 16277 break; 16278 } 16279 } 16280 } 16281 16282 if ( ! fieldName ) { 16283 throw 'Could not automatically determine field data. '+ 16284 'Please see https://datatables.net/tn/11'; 16285 } 16286 16287 if ( ! idValues[ fieldName ] ) { 16288 idValues[ fieldName ] = {}; 16289 } 16290 16291 var id = dt.row( cell.index.row ).id(); 16292 idValues[ fieldName ][ id ] = cell.set; 16293 16294 // Keep a list of cells so we can activate the bubble editing 16295 // with them 16296 nodes.push( cell.index ); 16297 } 16298 } 16299 16300 // Perform the edit using bubble editing as it allows us to specify 16301 // the cells to be edited, rather than using full rows 16302 editor 16303 .bubble( nodes, false ) 16304 .multiSet( idValues ) 16305 .submit(); 16306 }, 16307 16308 16309 /** 16310 * Emit an event on the DataTable for listeners 16311 * 16312 * @param {string} name Event name 16313 * @param {array} args Event arguments 16314 * @private 16315 */ 16316 _emitEvent: function ( name, args ) 16317 { 16318 this.s.dt.iterator( 'table', function ( ctx, i ) { 16319 $(ctx.nTable).triggerHandler( name+'.dt', args ); 16320 } ); 16321 }, 16322 16323 16324 /** 16325 * Attach suitable listeners (based on the configuration) that will attach 16326 * and detach the AutoFill handle in the document. 16327 * 16328 * @private 16329 */ 16330 _focusListener: function () 16331 { 16332 var that = this; 16333 var dt = this.s.dt; 16334 var namespace = this.s.namespace; 16335 var focus = this.c.focus !== null ? 16336 this.c.focus : 16337 dt.init().keys || dt.settings()[0].keytable ?
16338 'focus' : 16339 'hover'; 16340 16341 // All event listeners attached here are removed in the `destroy` 16342 // callback in the constructor 16343 if ( focus === 'focus' ) { 16344 dt 16345 .on( 'key-focus.autoFill', function ( e, dt, cell ) { 16346 that._attach( cell.node() ); 16347 } ) 16348 .on( 'key-blur.autoFill', function ( e, dt, cell ) { 16349 that._detach(); 16350 } ); 16351 } 16352 else if ( focus === 'click' ) { 16353 $(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) { 16354 that._attach( this ); 16355 } ); 16356 16357 $(document.body).on( 'click'+namespace, function (e) { 16358 if ( ! $(e.target).parents().filter( dt.table().body() ).length ) { 16359 that._detach(); 16360 } 16361 } ); 16362 } 16363 else { 16364 $(dt.table().body()) 16365 .on( 'mouseenter'+namespace+' touchstart'+namespace, 'td, th', function (e) { 16366 that._attach( this ); 16367 } ) 16368 .on( 'mouseleave'+namespace+'touchend'+namespace, function (e) { 16369 if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) { 16370 return; 16371 } 16372 16373 that._detach(); 16374 } ); 16375 16376 16377 } 16378 }, 16379 16380 16381 _focusListenerRemove: function () 16382 { 16383 var dt = this.s.dt; 16384 16385 dt.off( '.autoFill' ); 16386 $(dt.table().body()).off( this.s.namespace ); 16387 $(document.body).off( this.s.namespace ); 16388 }, 16389 16390 16391 /** 16392 * Get the position of a node, relative to another, including any scrolling 16393 * offsets. 16394 * @param {Node} node Node to get the position of 16395 * @param {jQuery} targetParent Node to use as the parent 16396 * @return {object} Offset calculation 16397 * @private 16398 */ 16399 _getPosition: function ( node, targetParent ) 16400 { 16401 var 16402 currNode = node, 16403 currOffsetParent, 16404 top = 0, 16405 left = 0; 16406 16407 if ( ! targetParent ) { 16408 targetParent = $( $( this.s.dt.table().node() )[0].offsetParent ); 16409 } 16410 16411 do { 16412 // Don't use jQuery().position() the behaviour changes between 1.x and 3.x for 16413 // tables 16414 var positionTop = currNode.offsetTop; 16415 var positionLeft = currNode.offsetLeft; 16416 16417 // jQuery doesn't give a `table` as the offset parent oddly, so use DOM directly 16418 currOffsetParent = $( currNode.offsetParent ); 16419 16420 top += positionTop + parseInt( currOffsetParent.css('border-top-width') || 0 ) * 1; 16421 left += positionLeft + parseInt( currOffsetParent.css('border-left-width') || 0 ) * 1; 16422 16423 // Emergency fall back. Shouldn't happen, but just in case! 16424 if ( currNode.nodeName.toLowerCase() === 'body' ) { 16425 break; 16426 } 16427 16428 currNode = currOffsetParent.get(0); // for next loop 16429 } 16430 while ( currOffsetParent.get(0) !== targetParent.get(0) ) 16431 16432 return { 16433 top: top, 16434 left: left 16435 }; 16436 }, 16437 16438 16439 /** 16440 * Start mouse drag - selects the start cell 16441 * 16442 * @param {object} e Mouse down event 16443 * @private 16444 */ 16445 _mousedown: function ( e ) 16446 { 16447 var that = this; 16448 var dt = this.s.dt; 16449 16450 this.dom.start = this.dom.attachedTo; 16451 this.s.start = { 16452 row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ), 16453 column: $(this.dom.start).index() 16454 }; 16455 16456 $(document.body) 16457 .on( 'mousemove.autoFill touchmove.autoFill', function (e) { 16458 that._mousemove( e ); 16459 // If it is a touch event then when the touch ends we need to remove the handle 16460 if(e.type === 'touchmove') { 16461 $(document.body).one('touchend.autoFill', function() { 16462 that._detach(); 16463 }) 16464 } 16465 } ) 16466 .on( 'mouseup.autoFill touchend.autoFill', function (e) { 16467 that._mouseup( e ); 16468 } ); 16469 16470 var select = this.dom.select; 16471 var offsetParent = $( dt.table().node() ).offsetParent(); 16472 select.top.appendTo( offsetParent ); 16473 select.left.appendTo( offsetParent ); 16474 select.right.appendTo( offsetParent ); 16475 select.bottom.appendTo( offsetParent ); 16476 16477 this._drawSelection( this.dom.start, e ); 16478 16479 this.dom.handle.css( 'display', 'none' ); 16480 16481 // Cache scrolling information so mouse move doesn't need to read. 16482 // This assumes that the window and DT scroller will not change size 16483 // during an AutoFill drag, which I think is a fair assumption 16484 var scrollWrapper = this.dom.dtScroll; 16485 this.s.scroll = { 16486 windowHeight: $(window).height(), 16487 windowWidth: $(window).width(), 16488 dtTop: scrollWrapper ? scrollWrapper.offset().top : null, 16489 dtLeft: scrollWrapper ? scrollWrapper.offset().left : null, 16490 dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null, 16491 dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null 16492 }; 16493 }, 16494 16495 16496 /** 16497 * Mouse drag - selects the end cell and update the selection display for 16498 * the end user 16499 * 16500 * @param {object} e Mouse move event 16501 * @private 16502 */ 16503 _mousemove: function ( e ) 16504 { 16505 var target = e.touches && e.touches.length 16506 ? document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) 16507 : e.target; 16508 var name = target.nodeName.toLowerCase(); 16509 16510 if ( name !== 'td' && name !== 'th' ) { 16511 return; 16512 } 16513 16514 this._drawSelection( target, e ); 16515 this._shiftScroll( e ); 16516 }, 16517 16518 16519 /** 16520 * End mouse drag - perform the update actions 16521 * 16522 * @param {object} e Mouse up event 16523 * @private 16524 */ 16525 _mouseup: function ( e ) 16526 { 16527 $(document.body).off( '.autoFill' ); 16528 16529 var that = this; 16530 var dt = this.s.dt; 16531 var select = this.dom.select; 16532 select.top.remove(); 16533 select.left.remove(); 16534 select.right.remove(); 16535 select.bottom.remove(); 16536 16537 this.dom.handle.css( 'display', 'block' ); 16538 16539 // Display complete - now do something useful with the selection! 16540 var start = this.s.start; 16541 var end = this.s.end; 16542 16543 // Haven't selected multiple cells, so nothing to do 16544 if ( start.row === end.row && start.column === end.column ) { 16545 return; 16546 } 16547 16548 var startDt = dt.cell( ':eq('+start.row+')', start.column+':visible', {page:'current'} ); 16549 16550 // If Editor is active inside this cell (inline editing) we need to wait for Editor to 16551 // submit and then we can loop back and trigger the fill. 16552 if ( $('div.DTE', startDt.node()).length ) { 16553 var editor = dt.editor(); 16554 16555 editor 16556 .on( 'submitSuccess.dtaf close.dtaf', function () { 16557 editor.off( '.dtaf'); 16558 16559 setTimeout( function () { 16560 that._mouseup( e ); 16561 }, 100 ); 16562 } ) 16563 .on( 'submitComplete.dtaf preSubmitCancelled.dtaf close.dtaf', function () { 16564 editor.off( '.dtaf'); 16565 } ); 16566 16567 // Make the current input submit 16568 editor.submit(); 16569 16570 return; 16571 } 16572 16573 // Build a matrix representation of the selected rows 16574 var rows = this._range( start.row, end.row ); 16575 var columns = this._range( start.column, end.column ); 16576 var selected = []; 16577 var dtSettings = dt.settings()[0]; 16578 var dtColumns = dtSettings.aoColumns; 16579 var enabledColumns = dt.columns( this.c.columns ).indexes(); 16580 16581 // Can't use Array.prototype.map as IE8 doesn't support it 16582 // Can't use $.map as jQuery flattens 2D arrays
16583 // Need to use a good old fashioned for loop 16584 for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) { 16585 selected.push( 16586 $.map( columns, function (column) { 16587 var row = dt.row( ':eq('+rows[rowIdx]+')', {page:'current'} ); // Workaround for M581 16588 var cell = dt.cell( row.index(), column+':visible' ); 16589 var data = cell.data(); 16590 var cellIndex = cell.index(); 16591 var editField = dtColumns[ cellIndex.column ].editField; 16592 16593 if ( editField !== undefined ) { 16594 data = dtSettings.oApi._fnGetObjectDataFn( editField )( dt.row( cellIndex.row ).data() ); 16595 } 16596 16597 if ( enabledColumns.indexOf(cellIndex.column) === -1 ) { 16598 return; 16599 } 16600 16601 return { 16602 cell: cell, 16603 data: data, 16604 label: cell.data(), 16605 index: cellIndex 16606 }; 16607 } ) 16608 ); 16609 } 16610 16611 this._actionSelector( selected ); 16612 16613 // Stop shiftScroll 16614 clearInterval( this.s.scrollInterval ); 16615 this.s.scrollInterval = null; 16616 }, 16617 16618 16619 /** 16620 * Create an array with a range of numbers defined by the start and end 16621 * parameters passed in (inclusive!). 16622 * 16623 * @param {integer} start Start 16624 * @param {integer} end End 16625 * @private 16626 */ 16627 _range: function ( start, end ) 16628 { 16629 var out = []; 16630 var i; 16631 16632 if ( start <= end ) { 16633 for ( i=start ; i<=end ; i++ ) { 16634 out.push( i ); 16635 } 16636 } 16637 else { 16638 for ( i=start ; i>=end ; i-- ) { 16639 out.push( i ); 16640 } 16641 } 16642 16643 return out; 16644 }, 16645 16646 16647 /** 16648 * Move the window and DataTables scrolling during a drag to scroll new 16649 * content into view. This is done by proximity to the edge of the scrolling 16650 * container of the mouse - for example near the top edge of the window 16651 * should scroll up. This is a little complicated as there are two elements 16652 * that can be scrolled - the window and the DataTables scrolling view port 16653 * (if scrollX and / or scrollY is enabled). 16654 * 16655 * @param {object} e Mouse move event object 16656 * @private 16657 */ 16658 _shiftScroll: function ( e ) 16659 { 16660 var that = this; 16661 var dt = this.s.dt; 16662 var scroll = this.s.scroll; 16663 var runInterval = false; 16664 var scrollSpeed = 5; 16665 var buffer = 65; 16666 16667 // Different values if using a touchscreen 16668 var pageX = !e.type.includes('touch') ? e.pageX - window.scrollX :e.touches[0].clientX; 16669 var pageY = !e.type.includes('touch') ? e.pageY - window.scrollY :e.touches[0].clientY; 16670 var 16671 windowY = pageY, 16672 windowX = pageX, 16673 windowVert, windowHoriz, 16674 dtVert, dtHoriz; 16675 16676 // Window calculations - based on the mouse position in the window, 16677 // regardless of scrolling 16678 if ( windowY < buffer ) { 16679 windowVert = scrollSpeed * -1; 16680 } 16681 else if ( windowY > scroll.windowHeight - buffer ) { 16682 windowVert = scrollSpeed; 16683 } 16684 16685 if ( windowX < buffer ) { 16686 windowHoriz = scrollSpeed * -1; 16687 } 16688 else if ( windowX > scroll.windowWidth - buffer ) { 16689 windowHoriz = scrollSpeed; 16690 } 16691 16692 // DataTables scrolling calculations - based on the table's position in 16693 // the document and the mouse position on the page 16694 if ( scroll.dtTop !== null && pageY < scroll.dtTop + buffer ) { 16695 dtVert = scrollSpeed * -1; 16696 } 16697 else if ( scroll.dtTop !== null && pageY > scroll.dtTop + scroll.dtHeight - buffer ) { 16698 dtVert = scrollSpeed; 16699 } 16700 16701 if ( scroll.dtLeft !== null && pageX < scroll.dtLeft + buffer ) { 16702 dtHoriz = scrollSpeed * -1; 16703 } 16704 else if ( scroll.dtLeft !== null && pageX > scroll.dtLeft + scroll.dtWidth - buffer ) { 16705 dtHoriz = scrollSpeed; 16706 } 16707 16708 // This is where it gets interesting. We want to continue scrolling 16709 // without requiring a mouse move, so we need an interval to be 16710 // triggered. The interval should continue until it is no longer needed, 16711 // but it must also use the latest scroll commands (for example consider 16712 // that the mouse might move from scrolling up to scrolling left, all 16713 // with the same interval running. We use the `scroll` object to "pass" 16714 // this information to the interval. Can't use local variables as they 16715 // wouldn't be the ones that are used by an already existing interval! 16716 if ( windowVert || windowHoriz || dtVert || dtHoriz ) { 16717 scroll.windowVert = windowVert; 16718 scroll.windowHoriz = windowHoriz; 16719 scroll.dtVert = dtVert; 16720 scroll.dtHoriz = dtHoriz; 16721 runInterval = true; 16722 } 16723 else if ( this.s.scrollInterval ) { 16724 // Don't need to scroll - remove any existing timer 16725 clearInterval( this.s.scrollInterval ); 16726 this.s.scrollInterval = null; 16727 } 16728 16729 // If we need to run the interval to scroll and there is no existing 16730 // interval (if there is an existing one, it will continue to run) 16731 if ( ! this.s.scrollInterval && runInterval ) { 16732 this.s.scrollInterval = setInterval( function () { 16733 // Don't need to worry about setting scroll <0 or beyond the 16734 // scroll bound as the browser will just reject that. 16735 window.scrollTo(window.scrollX + (scroll.windowHoriz ? scroll.windowHoriz : 0), window.scrollY + (scroll.windowVert ? scroll.windowVert : 0)) 16736 16737 // DataTables scrolling 16738 if ( scroll.dtVert || scroll.dtHoriz ) { 16739 var scroller = that.dom.dtScroll[0]; 16740 16741 if ( scroll.dtVert ) { 16742 scroller.scrollTop += scroll.dtVert; 16743 } 16744 if ( scroll.dtHoriz ) { 16745 scroller.scrollLeft += scroll.dtHoriz; 16746 } 16747 } 16748 }, 20 ); 16749 } 16750 }, 16751 16752 16753 /** 16754 * Update the DataTable after the user has selected what they want to do 16755 * 16756 * @param {false|undefined} result Return from the `execute` method - can 16757 * be false internally to do nothing. This is not documented for plug
16757-ins 16758 * and is used only by the cancel option. 16759 * @param {array} cells Information about the selected cells from the key 16760 * up function, argumented with the set values 16761 * @private 16762 */ 16763 _update: function ( result, cells ) 16764 { 16765 // Do nothing on `false` return from an execute function 16766 if ( result === false ) { 16767 return; 16768 } 16769 16770 var dt = this.s.dt; 16771 var cell; 16772 var columns = dt.columns( this.c.columns ).indexes(); 16773 16774 // Potentially allow modifications to the cells matrix 16775 this._emitEvent( 'preAutoFill', [ dt, cells ] ); 16776 16777 this._editor( cells ); 16778 16779 // Automatic updates are not performed if `update` is null and the 16780 // `editor` parameter is passed in - the reason being that Editor will 16781 // update the data once submitted 16782 var update = this.c.update !== null ? 16783 this.c.update : 16784 this.c.editor ? 16785 false : 16786 true; 16787 16788 if ( update ) { 16789 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16790 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16791 cell = cells[i][j]; 16792 16793 if ( columns.indexOf(cell.index.column) !== -1 ) { 16794 cell.cell.data( cell.set ); 16795 } 16796 } 16797 } 16798 16799 dt.draw(false); 16800 } 16801 16802 this._emitEvent( 'autoFill', [ dt, cells ] ); 16803 } 16804} ); 16805 16806 16807/** 16808 * AutoFill actions. The options here determine how AutoFill will fill the data 16809 * in the table when the user has selected a range of cells. Please see the 16810 * documentation on the DataTables site for full details on how to create plug- 16811 * ins. 16812 * 16813 * @type {Object} 16814 */ 16815AutoFill.actions = { 16816 increment: { 16817 available: function ( dt, cells ) { 16818 var d = cells[0][0].label; 16819 16820 // is numeric test based on jQuery's old `isNumeric` function 16821 return !isNaN( d - parseFloat( d ) ); 16822 }, 16823 16824 option: function ( dt, cells ) { 16825 return dt.i18n( 16826 'autoFill.increment', 16827 'Increment / decrement each cell by: <input type="number" value="1">' 16828 ); 16829 }, 16830 16831 execute: function ( dt, cells, node ) { 16832 var value = cells[0][0].data * 1; 16833 var increment = $('input', node).val() * 1; 16834 16835 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16836 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16837 cells[i][j].set = value; 16838 16839 value += increment; 16840 } 16841 } 16842 } 16843 }, 16844 16845 fill: { 16846 available: function ( dt, cells ) { 16847 return true; 16848 }, 16849 16850 option: function ( dt, cells ) { 16851 return dt.i18n('autoFill.fill', 'Fill all cells with <i>%d</i>', cells[0][0].label ); 16852 }, 16853 16854 execute: function ( dt, cells, node ) { 16855 var value = cells[0][0].data; 16856 16857 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16858 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16859 cells[i][j].set = value; 16860 } 16861 } 16862 } 16863 }, 16864 16865 fillHorizontal: { 16866 available: function ( dt, cells ) { 16867 return cells.length > 1 && cells[0].length > 1; 16868 }, 16869 16870 option: function ( dt, cells ) { 16871 return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' ); 16872 }, 16873 16874 execute: function ( dt, cells, node ) { 16875 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16876 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16877 cells[i][j].set = cells[i][0].data; 16878 } 16879 } 16880 } 16881 }, 16882 16883 fillVertical: { 16884 available: function ( dt, cells ) { 16885 return cells.length > 1 && cells[0].length > 1; 16886 }, 16887 16888 option: function ( dt, cells ) { 16889 return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' ); 16890 }, 16891 16892 execute: function ( dt, cells, node ) { 16893 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 16894 for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { 16895 cells[i][j].set = cells[0][j].data; 16896 } 16897 } 16898 } 16899 }, 16900 16901 // Special type that does not make itself available, but is added 16902 // automatically by AutoFill if a multi-choice list is shown. This allows 16903 // sensible code reuse 16904 cancel: { 16905 available: function () { 16906 return false; 16907 }, 16908 16909 option: function ( dt ) { 16910 return dt.i18n('autoFill.cancel', 'Cancel' ); 16911 }, 16912 16913 execute: function () { 16914 return false; 16915 } 16916 } 16917}; 16918 16919 16920/** 16921 * AutoFill version 16922 * 16923 * @static 16924 * @type String 16925 */ 16926AutoFill.version = '2.5.3'; 16927 16928 16929/** 16930 * AutoFill defaults 16931 * 16932 * @namespace 16933 */ 16934AutoFill.defaults = { 16935 /** @type {Boolean} Ask user what they want to do, even for a single option */ 16936 alwaysAsk: false, 16937 16938 closeButton: true, 16939 16940 /** @type {string|null} What will trigger a focus */ 16941 focus: null, // focus, click, hover 16942 16943 /** @type {column-selector} Columns to provide auto fill for */ 16944 columns: '', // all 16945 16946 /** @type {Boolean} Enable AutoFill on load */ 16947 enable: true, 16948 16949 /** @type {boolean|null} Update the cells after a drag */ 16950 update: null, // false is editor given, true otherwise 16951 16952 /** @type {DataTable.Editor} Editor instance for automatic submission */ 16953 editor: null, 16954 16955 /** @type {boolean} Enable vertical fill */ 16956 vertical: true, 16957 16958 /** @type {boolean} Enable horizontal fill */ 16959 horizontal: true 16960}; 16961 16962 16963/** 16964 * Classes used by AutoFill that are configurable 16965 * 16966 * @namespace 16967 */ 16968AutoFill.classes = { 16969 /** @type {String} Class used by the selection button */ 16970 btn: 'btn', 16971 16972 closeable: 'dtaf-popover-closeable' 16973}; 16974 16975 16976/* 16977 * API 16978 */ 16979var Api = $.fn.dataTable.Api; 16980 16981// Doesn't do anything - Not documented 16982Api.register( 'autoFill()', function () { 16983 return this; 16984} ); 16985 16986Api.register( 'autoFill().enabled()', function () { 16987 var ctx = this.context[0]; 16988 16989 return ctx.autoFill ? 16990 ctx.autoFill.enabled() : 16991 false; 16992} ); 16993 16994Api.register( 'autoFill().enable()', function ( flag ) { 16995 return this.iterator( 'table', function ( ctx ) { 16996 if ( ctx.autoFill ) { 16997 ctx.autoFill.enable( flag ); 16998 } 16999 } ); 17000} ); 17001 17002Api.register( 'autoFill().disable()', function () { 17003 return this.iterator( 'table', function ( ctx ) { 17004 if ( ctx.autoFill ) { 17005 ctx.autoFill.disable(); 17006 } 17007 } ); 17008} ); 17009 17010 17011// Attach a listener to the document which listens for DataTables initialisation 17012// events so we can automatically initialise 17013$(document).on( 'preInit.dt.autofill', function (e, settings, json) { 17014 if ( e.namespace !== 'dt' ) { 17015 return; 17016 } 17017 17018 var init = settings.oInit.autoFill; 17019 var defaults = DataTable.defaults.autoFill; 17020 17021 if ( init || defaults ) { 17022 var opts = $.extend( {}, init, defaults ); 17023 17024 if ( init !== false ) { 17025 new AutoFill( settings, opts ); 17026 } 17027 } 17028} ); 17029 17030 17031// Alias for access 17032DataTable.AutoFill = AutoFill; 17033$.fn.DataTable.AutoFill = AutoFill; 17034 17035 17036return DataTable; 17037})); 17038 17039 17040/*! DataTables styling wrapper for AutoFill 17041 * ©2018 SpryMedia Ltd - datatables.net/license 17042 */ 17043 17044(function( factory ){ 17045 if ( typeof define === 'function' && define.amd ) { 17046 // AMD 17047 define( ['jquery', 'datatables.net-dt', 'datatables.net-autofill'], function ( $ ) { 17048 return factory( $, window, document ); 17049 } ); 17050 } 17051 else if ( typeof exports === 'object' ) { 17052 // CommonJS 17053 var jq = require('jquery'); 17054 var cjsRequires = function (root, $) { 17055 if ( ! $.fn.dataTable ) { 17056 require('datatables.net-dt')(root, $); 17057 } 17058 17059 if ( ! $.fn.dataTable.AutoFill ) { 17060 require('datatables.net-autofill')(root, $); 17061 } 17062 }; 17063 17064 if (typeof window !== 'undefined') { 17065 module.exports = function (root, $) { 17066 if ( ! root ) { 17067 // CommonJS environments without a window global must pass a 17068 // root. This will give an error otherwise 17069 root = window; 17070 } 17071 17072 if ( ! $ ) { 17073 $ = jq( root ); 17074 } 17075 17076 cjsRequires( root, $ ); 17077 return factory( $, root, root.document ); 17078 }; 17079 } 17080 else { 17081 cjsRequires( window, jq ); 17082 module.exports = factory( jq, window, window.document ); 17083 } 17084 } 17085 else { 17086 // Browser 17087 factory( jQuery, window, document ); 17088 } 17089}(function( $, window, document, undefined ) { 17090'use strict'; 17091var DataTable = $.fn.dataTable; 17092 17093 17094 17095 17096return DataTable; 17097})); 17098 17099 17100/*! Buttons for DataTables 2.3.6 17101 * ©2016-2023 SpryMedia Ltd - datatables.net/license 17102 */ 17103 17104(function( factory ){ 17105 if ( typeof define === 'function' && define.amd ) { 17106 // AMD 17107 define( ['jquery', 'datatables.net'], function ( $ ) { 17108 return factory( $, window, document ); 17109 } ); 17110 } 17111 else if ( typeof exports === 'object' ) { 17112 // CommonJS 17113 var jq = require('jquery'); 17114 var cjsRequires = function (root, $) { 17115 if ( ! $.fn.dataTable ) { 17116 require('datatables.net')(root, $); 17117 } 17118 }; 17119 17120 if (typeof window !== 'undefined') { 17121 module.exports = function (root, $) { 17122 if ( ! root ) { 17123 // CommonJS environments without a window global must pass a 17124 // root. This will give an error otherwise 17125 root = window; 17126 } 17127 17128 if ( ! $ ) { 17129 $ = jq( root ); 17130 } 17131 17132 cjsRequires( root, $ ); 17133 return factory( $, root, root.document ); 17134 }; 17135 } 17136 else { 17137 cjsRequires( window, jq ); 17138 module.exports = factory( jq, window, window.document ); 17139 } 17140 } 17141 else { 17142 // Browser 17143 factory( jQuery, window, document ); 17144 } 17145}(function( $, window, document, undefined ) { 17146'use strict'; 17147var DataTable = $.fn.dataTable; 17148 17149 17150 17151// Used for namespacing events added to the document by each instance, so they 17152// can be removed on destroy 17153var _instCounter = 0; 17154 17155// Button namespacing counter for namespacing events on individual buttons 17156var _buttonCounter = 0; 17157 17158var _dtButtons = DataTable.ext.buttons; 17159 17160// Allow for jQuery slim 17161function _fadeIn(el, duration, fn) { 17162 if ($.fn.animate) { 17163 el 17164 .stop() 17165 .fadeIn( duration, fn ); 17166 17167 } 17168 else { 17169 el.css('display', 'block'); 17170 17171 if (fn) { 17172 fn.call(el); 17173 } 17174 } 17175} 17176 17177function _fadeOut(el, duration, fn) { 17178 if ($.fn.animate) { 17179 el 17180 .stop() 17181 .fadeOut( duration, fn ); 17182 } 17183 else { 17184 el.css('display', 'none'); 17185 17186 if (fn) { 17187 fn.call(el); 17188 } 17189 } 17190} 17191 17192/** 17193 * [Buttons description] 17194 * @param {[type]} 17195 * @param {[type]} 17196 */ 17197var Buttons = function( dt, config ) 17198{ 17199 // If not created with a `new` keyword then we return a wrapper function that 17200 // will take the settings object for a DT. This allows easy use of new instances 17201 // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`. 17202 if ( !(this instanceof Buttons) ) { 17203 return function (settings) { 17204 return new Buttons( settings, dt ).container(); 17205 }; 17206 } 17207 17208 // If there is no config set it to an empty object 17209 if ( typeof( config ) === 'undefined' ) { 17210 config = {}; 17211 } 17212 17213 // Allow a boolean true for defaults 17214 if ( config === true ) { 17215 config = {}; 17216 } 17217 17218 // For easy configuration of buttons an array can be given 17219 if ( Array.isArray( config ) ) { 17220 config = { buttons: config }; 17221 } 17222 17223 this.c = $.extend( true, {}, Buttons.defaults, config ); 17224 17225 // Don't want a deep copy for the buttons 17226 if ( config.buttons ) { 17227 this.c.buttons = config.buttons; 17228 } 17229 17230 this.s = { 17231 dt: new DataTable.Api( dt ), 17232 buttons: [], 17233 listenKeys: '', 17234 namespace: 'dtb'+(_instCounter++) 17235 }; 17236 17237 this.dom = { 17238 container: $('<'+this.c.dom.container.tag+'/>') 17239 .addClass( this.c.dom.container.className ) 17240 }; 17241 17242 this._constructor(); 17243}; 17244 17245 17246$.extend( Buttons.prototype, { 17247 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 17248 * Public methods 17249 */ 17250 17251 /** 17252 * Get the action of a button 17253 * @param {int|string} Button index 17254 * @return {function} 17255 *//** 17256 * Set the action of a button 17257 * @param {node} node Button element 17258 * @param {function} action Function to set 17259 * @return {Buttons} Self for chaining 17260 */ 17261 action: function ( node, action ) 17262 { 17263 var button = this._nodeToButton( node ); 17264 17265 if ( action === undefined ) { 17266 return button.conf.action; 17267 } 17268 17269 button.conf.action = action; 17270 17271 return this; 17272 }, 17273 17274 /** 17275 * Add an active class to the button to make to look active or get current 17276 * active state. 17277 * @param {node} node Button element 17278 * @param {boolean} [flag] Enable / disable flag 17279 * @return {Buttons} Self for chaining or boolean for getter 17280 */ 17281 active: function ( node, flag ) { 17282 var button = this._nodeToButton( node ); 17283 var klass = this.c.dom.button.active; 17284 var jqNode = $(button.node); 17285 17286 if ( flag === undefined ) { 17287 return jqNode.hasClass( klass ); 17288 } 17289 17290 jqNode.toggleClass( klass, flag === undefined ? true : flag ); 17291 17292 return this; 17293 }, 17294 17295 /** 17296 * Add a new button 17297 * @param {object} config Button configuration object, base string name or function 17298 * @param {int|string} [idx] Button index for where to insert the button 17299 * @param {boolean} [draw=true] Trigger a draw. Set a false when adding 17300 * lots of buttons, until the last button. 17301 * @return {Buttons} Self for chaining 17302 */ 17303 add: function ( config, idx, draw ) 17304 { 17305 var buttons = this.s.buttons; 17306 17307 if ( typeof idx === 'string' ) { 17308 var split = idx.split('-'); 17309 var base = this.s; 17310 17311 for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) { 17312 base = base.buttons[ split[i]*1 ]; 17313 } 17314 17315 buttons = base.buttons; 17316 idx = split[ split.length-1 ]*1; 17317 } 17318 17319 this._expandButton( 17320 buttons, 17321 config,
17322 config !== undefined ? config.split : undefined, 17323 (config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined, 17324 false, 17325 idx 17326 ); 17327 17328 if (draw === undefined || draw === true) { 17329 this._draw(); 17330 } 17331 17332 return this; 17333 }, 17334 17335 /** 17336 * Clear buttons from a collection and then insert new buttons 17337 */ 17338 collectionRebuild: function ( node, newButtons ) 17339 { 17340 var button = this._nodeToButton( node ); 17341 17342 if(newButtons !== undefined) { 17343 var i; 17344 // Need to reverse the array 17345 for (i=button.buttons.length-1; i>=0; i--) { 17346 this.remove(button.buttons[i].node); 17347 } 17348 17349 // If the collection has prefix and / or postfix buttons we need to add them in 17350 if (button.conf.prefixButtons) { 17351 newButtons.unshift.apply(newButtons, button.conf.prefixButtons); 17352 } 17353 17354 if (button.conf.postfixButtons) { 17355 newButtons.push.apply(newButtons, button.conf.postfixButtons); 17356 } 17357 17358 for (i=0; i<newButtons.length; i++) { 17359 var newBtn = newButtons[i]; 17360 17361 this._expandButton( 17362 button.buttons, 17363 newBtn, 17364 newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined, 17365 true, 17366 newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined, 17367 null, 17368 newBtn.parentConf 17369 ); 17370 } 17371 } 17372 17373 this._draw(button.collection, button.buttons); 17374 }, 17375 17376 /** 17377 * Get the container node for the buttons 17378 * @return {jQuery} Buttons node 17379 */ 17380 container: function () 17381 { 17382 return this.dom.container; 17383 }, 17384 17385 /** 17386 * Disable a button 17387 * @param {node} node Button node 17388 * @return {Buttons} Self for chaining 17389 */ 17390 disable: function ( node ) { 17391 var button = this._nodeToButton( node ); 17392 17393 $(button.node) 17394 .addClass( this.c.dom.button.disabled ) 17395 .prop('disabled', true); 17396 17397 return this; 17398 }, 17399 17400 /** 17401 * Destroy the instance, cleaning up event handlers and removing DOM 17402 * elements 17403 * @return {Buttons} Self for chaining 17404 */ 17405 destroy: function () 17406 { 17407 // Key event listener 17408 $('body').off( 'keyup.'+this.s.namespace ); 17409 17410 // Individual button destroy (so they can remove their own events if 17411 // needed). Take a copy as the array is modified by `remove` 17412 var buttons = this.s.buttons.slice(); 17413 var i, ien; 17414 17415 for ( i=0, ien=buttons.length ; i<ien ; i++ ) { 17416 this.remove( buttons[i].node ); 17417 } 17418 17419 // Container 17420 this.dom.container.remove(); 17421 17422 // Remove from the settings object collection 17423 var buttonInsts = this.s.dt.settings()[0]; 17424 17425 for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) { 17426 if ( buttonInsts.inst === this ) { 17427 buttonInsts.splice( i, 1 ); 17428 break; 17429 } 17430 } 17431 17432 return this; 17433 }, 17434 17435 /** 17436 * Enable / disable a button 17437 * @param {node} node Button node 17438 * @param {boolean} [flag=true] Enable / disable flag 17439 * @return {Buttons} Self for chaining 17440 */ 17441 enable: function ( node, flag ) 17442 { 17443 if ( flag === false ) { 17444 return this.disable( node ); 17445 } 17446 17447 var button = this._nodeToButton( node ); 17448 $(button.node) 17449 .removeClass( this.c.dom.button.disabled ) 17450 .prop('disabled', false); 17451 17452 return this; 17453 }, 17454 17455 /** 17456 * Get a button's index 17457 * 17458 * This is internally recursive 17459 * @param {element} node Button to get the index of 17460 * @return {string} Button index 17461 */ 17462 index: function ( node, nested, buttons ) 17463 { 17464 if ( ! nested ) { 17465 nested = ''; 17466 buttons = this.s.buttons; 17467 } 17468 17469 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 17470 var inner = buttons[i].buttons; 17471 17472 if (buttons[i].node === node) { 17473 return nested + i; 17474 } 17475 17476 if ( inner && inner.length ) { 17477 var match = this.index(node, i + '-', inner); 17478 17479 if (match !== null) { 17480 return match; 17481 } 17482 } 17483 } 17484 17485 return null; 17486 }, 17487 17488 17489 /** 17490 * Get the instance name for the button set selector 17491 * @return {string} Instance name 17492 */ 17493 name: function () 17494 { 17495 return this.c.name; 17496 }, 17497 17498 /** 17499 * Get a button's node of the buttons container if no button is given 17500 * @param {node} [node] Button node 17501 * @return {jQuery} Button element, or container 17502 */ 17503 node: function ( node ) 17504 { 17505 if ( ! node ) { 17506 return this.dom.container; 17507 } 17508 17509 var button = this._nodeToButton( node ); 17510 return $(button.node); 17511 }, 17512 17513 /** 17514 * Set / get a processing class on the selected button 17515 * @param {element} node Triggering button node 17516 * @param {boolean} flag true to add, false to remove, undefined to get 17517 * @return {boolean|Buttons} Getter value or this if a setter. 17518 */ 17519 processing: function ( node, flag ) 17520 { 17521 var dt = this.s.dt; 17522 var button = this._nodeToButton( node ); 17523 17524 if ( flag === undefined ) { 17525 return $(button.node).hasClass( 'processing' ); 17526 } 17527 17528 $(button.node).toggleClass( 'processing', flag ); 17529 17530 $(dt.table().node()).triggerHandler( 'buttons-processing.dt', [ 17531 flag, dt.button( node ), dt, $(node), button.conf 17532 ] ); 17533 17534 return this; 17535 }, 17536 17537 /** 17538 * Remove a button. 17539 * @param {node} node Button node 17540 * @return {Buttons} Self for chaining 17541 */ 17542 remove: function ( node ) 17543 { 17544 var button = this._nodeToButton( node ); 17545 var host = this._nodeToHost( node ); 17546 var dt = this.s.dt; 17547 17548 // Remove any child buttons first 17549 if ( button.buttons.length ) { 17550 for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) { 17551 this.remove( button.buttons[i].node ); 17552 } 17553 } 17554 17555 button.conf.destroying = true; 17556 17557 // Allow the button to remove event handlers, etc 17558 if ( button.conf.destroy ) { 17559 button.conf.destroy.call( dt.button(node), dt, $(node), button.conf ); 17560 } 17561 17562 this._removeKey( button.conf ); 17563 17564 $(button.node).remove(); 17565 17566 var idx = $.inArray( button, host ); 17567 host.splice( idx, 1 ); 17568 17569 return this; 17570 }, 17571 17572 /** 17573 * Get the text for a button 17574 * @param {int|string}
17574 node Button index 17575 * @return {string} Button text 17576 *//** 17577 * Set the text for a button 17578 * @param {int|string|function} node Button index 17579 * @param {string} label Text 17580 * @return {Buttons} Self for chaining 17581 */ 17582 text: function ( node, label ) 17583 { 17584 var button = this._nodeToButton( node ); 17585 var buttonLiner = this.c.dom.collection.buttonLiner; 17586 var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ? 17587 buttonLiner.tag : 17588 this.c.dom.buttonLiner.tag; 17589 var dt = this.s.dt; 17590 var jqNode = $(button.node); 17591 var text = function ( opt ) { 17592 return typeof opt === 'function' ? 17593 opt( dt, jqNode, button.conf ) : 17594 opt; 17595 }; 17596 17597 if ( label === undefined ) { 17598 return text( button.conf.text ); 17599 } 17600 17601 button.conf.text = label; 17602 17603 if ( linerTag ) { 17604 jqNode 17605 .children( linerTag ) 17606 .eq(0) 17607 .filter(':not(.dt-down-arrow)') 17608 .html( text(label) ); 17609 } 17610 else { 17611 jqNode.html( text(label) ); 17612 } 17613 17614 return this; 17615 }, 17616 17617 17618 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 17619 * Constructor 17620 */ 17621 17622 /** 17623 * Buttons constructor 17624 * @private 17625 */ 17626 _constructor: function () 17627 { 17628 var that = this; 17629 var dt = this.s.dt; 17630 var dtSettings = dt.settings()[0]; 17631 var buttons = this.c.buttons; 17632 17633 if ( ! dtSettings._buttons ) { 17634 dtSettings._buttons = []; 17635 } 17636 17637 dtSettings._buttons.push( { 17638 inst: this, 17639 name: this.c.name 17640 } ); 17641 17642 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 17643 this.add( buttons[i] ); 17644 } 17645 17646 dt.on( 'destroy', function ( e, settings ) { 17647 if ( settings === dtSettings ) { 17648 that.destroy(); 17649 } 17650 } ); 17651 17652 // Global key event binding to listen for button keys 17653 $('body').on( 'keyup.'+this.s.namespace, function ( e ) { 17654 if ( ! document.activeElement || document.activeElement === document.body ) { 17655 // SUse a string of characters for fast lookup of if we need to 17656 // handle this 17657 var character = String.fromCharCode(e.keyCode).toLowerCase(); 17658 17659 if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) { 17660 that._keypress( character, e ); 17661 } 17662 } 17663 } ); 17664 }, 17665 17666 17667 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 17668 * Private methods 17669 */ 17670 17671 /** 17672 * Add a new button to the key press listener 17673 * @param {object} conf Resolved button configuration object 17674 * @private 17675 */ 17676 _addKey: function ( conf ) 17677 { 17678 if ( conf.key ) { 17679 this.s.listenKeys += $.isPlainObject( conf.key ) ? 17680 conf.key.key : 17681 conf.key; 17682 } 17683 }, 17684 17685 /** 17686 * Insert the buttons into the container. Call without parameters! 17687 * @param {node} [container] Recursive only - Insert point 17688 * @param {array} [buttons] Recursive only - Buttons array 17689 * @private 17690 */ 17691 _draw: function ( container, buttons ) 17692 { 17693 if ( ! container ) { 17694 container = this.dom.container; 17695 buttons = this.s.buttons; 17696 } 17697 17698 container.children().detach(); 17699 17700 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 17701 container.append( buttons[i].inserter ); 17702 container.append( ' ' ); 17703 17704 if ( buttons[i].buttons && buttons[i].buttons.length ) { 17705 this._draw( buttons[i].collection, buttons[i].buttons ); 17706 } 17707 } 17708 }, 17709 17710 /** 17711 * Create buttons from an array of buttons 17712 * @param {array} attachTo Buttons array to attach to 17713 * @param {object} button Button definition 17714 * @param {boolean} inCollection true if the button is in a collection 17715 * @private 17716 */ 17717 _expandButton: function ( attachTo, button, split, inCollection, inSplit, attachPoint, parentConf ) 17718 { 17719 var dt = this.s.dt; 17720 var buttonCounter = 0; 17721 var isSplit = false; 17722 var buttons = ! Array.isArray( button ) ? 17723 [ button ] : 17724 button; 17725 17726 if(button === undefined ) { 17727 buttons = !Array.isArray(split) ? 17728 [ split ] : 17729 split; 17730 } 17731 17732 if (button !== undefined && button.split !== undefined) { 17733 isSplit = true; 17734 } 17735 17736 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 17737 var conf = this._resolveExtends( buttons[i] ); 17738 17739 if ( ! conf ) { 17740 continue; 17741 } 17742 17743 if( conf.config !== undefined && conf.config.split) { 17744 isSplit = true; 17745 } 17746 else { 17747 isSplit = false; 17748 } 17749 17750 // If the configuration is an array, then expand the buttons at this 17751 // point 17752 if ( Array.isArray( conf ) ) { 17753 this._expandButton( attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf ); 17754 continue; 17755 } 17756 17757 var built = this._buildButton( conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit ); 17758 if ( ! built ) { 17759 continue; 17760 } 17761 17762 if ( attachPoint !== undefined && attachPoint !== null ) { 17763 attachTo.splice( attachPoint, 0, built ); 17764 attachPoint++; 17765 } 17766 else { 17767 attachTo.push( built ); 17768 } 17769 17770 17771 if ( built.conf.buttons || built.conf.split ) { 17772 built.collection = $('<'+(isSplit ? this.c.dom.splitCollection.tag : this.c.dom.collection.tag)+'/>'); 17773 17774 built.conf._collection = built.collection; 17775 17776 if(built.conf.split) { 17777 for(var j = 0; j < built.conf.split.length; j++) { 17778 if(typeof built.conf.split[j] === "object") { 17779 built.conf.split[j].parent = parentConf; 17780 if(built.conf.split[j].collectionLayout === undefined) { 17781 built.conf.split[j].collectionLayout = built.conf.collectionLayout; 17782 } 17783 if(built.conf.split[j].dropup === undefined) { 17784 built.conf.split[j].dropup = built.conf.dropup; 17785 } 17786 if(built.conf.split[j].fade === undefined) { 17787 built.conf.split[j].fade = built.conf.fade; 17788 } 17789 } 17790 } 17791 } 17792 else { 17793 $(built.node).append($('<span class="dt-down-arrow">'+this.c.dom.splitDropdown.text+'</span>')) 17794 } 17795 17796 this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf ); 17797 } 17798 built.conf.parent = parentConf; 17799 17800 // init call is made here, rather than buildButton as it needs to 17801 // be selectable, and for that it needs to be in the buttons array 17802 if ( conf.init ) { 17803 conf.init.call( dt.button( built.node ), dt, $(built.node), conf ); 17804 } 17805 17806 buttonCounter++; 17807 } 17808 }, 17809 17810 /** 17811 * Create an individual button 17812 * @param {object} config Resolved button configuration 17813 * @param {boolean} inCollection `true` if a collection button 17814 * @return {jQuery} Created button node (jQuery) 17815 * @private 17816 */ 17817 _buildButton: function ( config, inCollection, isSplit, inSplit ) 17818 { 17819 var buttonDom = this.c.dom.button; 17820 var linerDom = this.c.dom.buttonLiner; 17821 var collectionDom = this.c.dom.collection; 17822 var splitDom = this.c.dom.split; 17823 var splitCollectionDom = this.c.dom.splitCollection; 17824 var splitDropdownButton = this.c.dom.splitDropdownButton; 17825 var dt = this.s.dt; 17826 var text = function ( opt ) { 17827 return typeof opt === 'function' ? 17828 opt( dt, button, config ) : 17829 opt; 17830 }; 17831
17832 // Spacers don't do much other than insert an element into the DOM 17833 if (config.spacer) { 17834 var spacer = $('<span></span>') 17835 .addClass('dt-button-spacer ' + config.style + ' ' + buttonDom.spacerClass) 17836 .html(text(config.text)); 17837 17838 return { 17839 conf: config, 17840 node: spacer, 17841 inserter: spacer, 17842 buttons: [], 17843 inCollection: inCollection, 17844 isSplit: isSplit, 17845 inSplit: inSplit, 17846 collection: null 17847 }; 17848 } 17849 17850 if ( !isSplit && inSplit && splitCollectionDom ) { 17851 buttonDom = splitDropdownButton; 17852 } 17853 else if ( !isSplit && inCollection && collectionDom.button ) { 17854 buttonDom = collectionDom.button; 17855 } 17856 17857 if ( !isSplit && inSplit && splitCollectionDom.buttonLiner ) { 17858 linerDom = splitCollectionDom.buttonLiner 17859 } 17860 else if ( !isSplit && inCollection && collectionDom.buttonLiner ) { 17861 linerDom = collectionDom.buttonLiner; 17862 } 17863 17864 // Make sure that the button is available based on whatever requirements 17865 // it has. For example, PDF button require pdfmake 17866 if ( config.available && ! config.available( dt, config ) && !config.hasOwnProperty('html') ) { 17867 return false; 17868 } 17869 17870 var button; 17871 if(!config.hasOwnProperty('html')) { 17872 var action = function ( e, dt, button, config ) { 17873 config.action.call( dt.button( button ), e, dt, button, config ); 17874 17875 $(dt.table().node()).triggerHandler( 'buttons-action.dt', [ 17876 dt.button( button ), dt, button, config 17877 ] ); 17878 }; 17879 17880 var tag = config.tag || buttonDom.tag; 17881 var clickBlurs = config.clickBlurs === undefined 17882 ? true : 17883 config.clickBlurs; 17884 17885 button = $('<'+tag+'/>') 17886 .addClass( buttonDom.className ) 17887 .addClass( inSplit ? this.c.dom.splitDropdownButton.className : '') 17888 .attr( 'tabindex', this.s.dt.settings()[0].iTabIndex ) 17889 .attr( 'aria-controls', this.s.dt.table().node().id ) 17890 .on( 'click.dtb', function (e) { 17891 e.preventDefault(); 17892 17893 if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { 17894 action( e, dt, button, config ); 17895 } 17896 if( clickBlurs ) { 17897 button.trigger('blur'); 17898 } 17899 } ) 17900 .on( 'keypress.dtb', function (e) { 17901 if ( e.keyCode === 13 ) { 17902 e.preventDefault(); 17903 17904 if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { 17905 action( e, dt, button, config ); 17906 } 17907 } 17908 } ); 17909 17910 // Make `a` tags act like a link 17911 if ( tag.toLowerCase() === 'a' ) { 17912 button.attr( 'href', '#' ); 17913 } 17914 17915 // Button tags should have `type=button` so they don't have any default behaviour 17916 if ( tag.toLowerCase() === 'button' ) { 17917 button.attr( 'type', 'button' ); 17918 } 17919 17920 if ( linerDom.tag ) { 17921 var liner = $('<'+linerDom.tag+'/>') 17922 .html( text( config.text ) ) 17923 .addClass( linerDom.className ); 17924 17925 if ( linerDom.tag.toLowerCase() === 'a' ) { 17926 liner.attr( 'href', '#' ); 17927 } 17928 17929 button.append( liner ); 17930 } 17931 else { 17932 button.html( text( config.text ) ); 17933 } 17934 17935 if ( config.enabled === false ) { 17936 button.addClass( buttonDom.disabled ); 17937 } 17938 17939 if ( config.className ) { 17940 button.addClass( config.className ); 17941 } 17942 17943 if ( config.titleAttr ) { 17944 button.attr( 'title', text( config.titleAttr ) ); 17945 } 17946 17947 if ( config.attr ) { 17948 button.attr( config.attr ); 17949 } 17950 17951 if ( ! config.namespace ) { 17952 config.namespace = '.dt-button-'+(_buttonCounter++); 17953 } 17954 17955 if ( config.config !== undefined && config.config.split ) { 17956 config.split = config.config.split; 17957 } 17958 } 17959 else { 17960 button = $(config.html) 17961 } 17962 17963 var buttonContainer = this.c.dom.buttonContainer; 17964 var inserter; 17965 if ( buttonContainer && buttonContainer.tag ) { 17966 inserter = $('<'+buttonContainer.tag+'/>') 17967 .addClass( buttonContainer.className ) 17968 .append( button ); 17969 } 17970 else { 17971 inserter = button; 17972 } 17973 17974 this._addKey( config ); 17975 17976 // Style integration callback for DOM manipulation 17977 // Note that this is _not_ documented. It is currently 17978 // for style integration only 17979 if( this.c.buttonCreated ) { 17980 inserter = this.c.buttonCreated( config, inserter ); 17981 } 17982 17983 var splitDiv; 17984 if(isSplit) {
17985 splitDiv = $('<div/>').addClass(this.c.dom.splitWrapper.className) 17986 splitDiv.append(button); 17987 var dropButtonConfig = $.extend(config, { 17988 text: this.c.dom.splitDropdown.text, 17989 className: this.c.dom.splitDropdown.className, 17990 closeButton: false, 17991 attr: { 17992 'aria-haspopup': 'dialog', 17993 'aria-expanded': false 17994 }, 17995 align: this.c.dom.splitDropdown.align, 17996 splitAlignClass: this.c.dom.splitDropdown.splitAlignClass 17997 17998 }) 17999 18000 this._addKey(dropButtonConfig); 18001 18002 var splitAction = function ( e, dt, button, config ) { 18003 _dtButtons.split.action.call( dt.button(splitDiv), e, dt, button, config ); 18004 18005 $(dt.table().node()).triggerHandler( 'buttons-action.dt', [ 18006 dt.button( button ), dt, button, config 18007 ] ); 18008 button.attr('aria-expanded', true) 18009 }; 18010 18011 var dropButton = $('<button class="' + this.c.dom.splitDropdown.className + ' dt-button"><span class="dt-btn-split-drop-arrow">'+this.c.dom.splitDropdown.text+'</span></button>') 18012 .on( 'click.dtb', function (e) { 18013 e.preventDefault(); 18014 e.stopPropagation(); 18015 18016 if ( ! dropButton.hasClass( buttonDom.disabled )) { 18017 splitAction( e, dt, dropButton, dropButtonConfig ); 18018 } 18019 if ( clickBlurs ) { 18020 dropButton.trigger('blur'); 18021 } 18022 } ) 18023 .on( 'keypress.dtb', function (e) { 18024 if ( e.keyCode === 13 ) { 18025 e.preventDefault(); 18026 18027 if ( ! dropButton.hasClass( buttonDom.disabled ) ) { 18028 splitAction( e, dt, dropButton, dropButtonConfig ); 18029 } 18030 } 18031 } ); 18032 18033 if(config.split.length === 0) { 18034 dropButton.addClass('dtb-hide-drop'); 18035 } 18036 18037 splitDiv.append(dropButton).attr(dropButtonConfig.attr); 18038 } 18039 18040 return { 18041 conf: config, 18042 node: isSplit ? splitDiv.get(0) : button.get(0), 18043 inserter: isSplit ? splitDiv : inserter, 18044 buttons: [], 18045 inCollection: inCollection, 18046 isSplit: isSplit, 18047 inSplit: inSplit, 18048 collection: null 18049 }; 18050 }, 18051 18052 /** 18053 * Get the button object from a node (recursive) 18054 * @param {node} node Button node 18055 * @param {array} [buttons] Button array, uses base if not defined 18056 * @return {object} Button object 18057 * @private 18058 */ 18059 _nodeToButton: function ( node, buttons ) 18060 { 18061 if ( ! buttons ) { 18062 buttons = this.s.buttons; 18063 } 18064 18065 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 18066 if ( buttons[i].node === node ) { 18067 return buttons[i]; 18068 } 18069 18070 if ( buttons[i].buttons.length ) { 18071 var ret = this._nodeToButton( node, buttons[i].buttons ); 18072 18073 if ( ret ) { 18074 return ret; 18075 } 18076 } 18077 } 18078 }, 18079 18080 /** 18081 * Get container array for a button from a button node (recursive) 18082 * @param {node} node Button node 18083 * @param {array} [buttons] Button array, uses base if not defined 18084 * @return {array} Button's host array 18085 * @private 18086 */ 18087 _nodeToHost: function ( node, buttons ) 18088 { 18089 if ( ! buttons ) { 18090 buttons = this.s.buttons; 18091 } 18092 18093 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 18094 if ( buttons[i].node === node ) { 18095 return buttons; 18096 } 18097 18098 if ( buttons[i].buttons.length ) { 18099 var ret = this._nodeToHost( node, buttons[i].buttons ); 18100 18101 if ( ret ) { 18102 return ret; 18103 } 18104 } 18105 } 18106 }, 18107 18108 /** 18109 * Handle a key press - determine if any button's key configured matches 18110 * what was typed and trigger the action if so. 18111 * @param {string} character The character pressed 18112 * @param {object} e Key event that triggered this call 18113 * @private 18114 */ 18115 _keypress: function ( character, e ) 18116 { 18117 // Check if this button press already activated on another instance of Buttons 18118 if ( e._buttonsHandled ) { 18119 return; 18120 } 18121 18122 var run = function ( conf, node ) { 18123 if ( ! conf.key ) { 18124 return; 18125 } 18126 18127 if ( conf.key === character ) { 18128 e._buttonsHandled = true; 18129 $(node).click(); 18130 } 18131 else if ( $.isPlainObject( conf.key ) ) { 18132 if ( conf.key.key !== character ) { 18133 return; 18134 } 18135 18136 if ( conf.key.shiftKey && ! e.shiftKey ) { 18137 return; 18138 } 18139 18140 if ( conf.key.altKey && ! e.altKey ) { 18141 return; 18142 } 18143 18144 if ( conf.key.ctrlKey && ! e.ctrlKey ) { 18145 return; 18146 } 18147 18148 if ( conf.key.metaKey && ! e.metaKey ) { 18149 return; 18150 } 18151 18152 // Made it this far - it is good 18153 e._buttonsHandled = true; 18154 $(node).click(); 18155 } 18156 }; 18157 18158 var recurse = function ( a ) { 18159 for ( var i=0, ien=a.length ; i<ien ; i++ ) { 18160 run( a[i].conf, a[i].node ); 18161 18162 if ( a[i].buttons.length ) { 18163 recurse( a[i].buttons ); 18164 } 18165 } 18166 }; 18167 18168 recurse( this.s.buttons ); 18169 }, 18170 18171 /** 18172 * Remove a key from the key listener for this instance (to be used when a 18173 * button is removed) 18174 * @param {object} conf Button configuration 18175 * @private 18176 */ 18177 _removeKey: function ( conf ) 18178 { 18179 if ( conf.key ) { 18180 var character = $.isPlainObject( conf.key ) ? 18181 conf.key.key : 18182 conf.key; 18183 18184 // Remove only one character, as multiple buttons could have the 18185 // same listening key 18186 var a = this.s.listenKeys.split(''); 18187 var idx = $.inArray( character, a ); 18188 a.splice( idx, 1 ); 18189 this.s.listenKeys = a.join(''); 18190 } 18191 }, 18192 18193 /** 18194 * Resolve a button configuration 18195 * @param {string|function|object} conf Button config to resolve 18196 * @return {object} Button configuration 18197 * @private 18198 */ 18199 _resolveExtends: function ( conf ) 18200 { 18201 var that = this; 18202 var dt = this.s.dt; 18203 var i, ien; 18204 var toConfObject = function ( base ) { 18205 var loop = 0; 18206 18207 // Loop until we have resolved to a button configuration, or an 18208 // array of button configurations (which will be iterated 18209 // separately) 18210 while ( ! $.isPlainObject(base) && ! Array.isArray(base) ) { 18211 if ( base === undefined ) { 18212 return; 18213 } 18214 18215 if ( typeof base === 'function' ) { 18216 base = base.call( that, dt, conf ); 18217 18218 if ( ! base ) { 18219 return false; 18220 } 18221 } 18222 else if ( typeof base === 'string' ) { 18223 if ( ! _dtButtons[ base ] ) { 18224 return {html: base} 18225 } 18226 18227 base = _dtButtons[ base ]; 18228 } 18229 18230 loop++; 18231 if ( loop > 30 ) { 18232 // Protect against misconfiguration killing the browser 18233 throw 'Buttons: Too many iterations'; 18234 } 18235 } 18236 18237 return Array.isArray( base ) ? 18238 base : 18239 $.extend( {}, base ); 18240 }; 18241 18242 conf = toConfObject( conf ); 18243 18244 while ( conf && conf.extend ) { 18245 // Use `toConfObject` in case the button definition being extended 18246 // is itself a string or a function 18247 if ( ! _dtButtons[ conf.extend ] ) { 18248 throw 'Cannot extend unknown button type: '+conf.extend; 18249 } 18250 18251 var objArray = toConfObject( _dtButtons[ conf.extend ] ); 18252 if ( Array.isArray( objArray ) ) { 18253 return objArray; 18254 } 18255 else if ( ! objArray ) { 18256 // This is a little brutal as it might be possible to have a 18257 // valid button without the extend, but if there is no extend 18258 // then the host button would be acting in an undefined state 18259 return false; 18260 } 18261 18262 // Stash the current class name 18263 var originalClassName = objArray.className; 18264 18265 if (conf.config !== undefined && objArray.config !== undefined) { 18266 conf.config = $.extend({}, objArray.config, conf.config) 18267 } 18268 18269 conf = $.extend( {}, objArray, conf ); 18270 18271 // The extend will have overwritten the original class name if the 18272 // `conf` object also assigned a class, but we want to concatenate 18273 // them so they are list that is combined from all extended buttons 18274 if ( originalClassName && conf.className !== originalClassName ) { 18275 conf.className = originalClassName+' '+conf.className; 18276 } 18277 18278 // Although we want the `conf` object to overwrite almost all of 18279 // the properties of the object being extended, the `extend` 18280 // property should come from the object being extended 18281 conf.extend = objArray.extend; 18282 } 18283 18284 // Buttons to be added to a collection -gives the ability to define 18285 // if buttons should be added to the start or end of a collection 18286 var postfixButtons = conf.postfixButtons; 18287 if ( postfixButtons ) { 18288 if ( ! conf.buttons ) { 18289 conf.buttons = []; 18290 } 18291 18292 for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) { 18293 conf.buttons.push( postfixButtons[i] ); 18294 } 18295 } 18296 18297 var prefixButtons = conf.prefixButtons; 18298 if ( prefixButtons ) { 18299 if ( ! conf.buttons ) { 18300 conf.buttons = []; 18301 } 18302 18303 for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) { 18304 conf.buttons.splice( i, 0, prefixButtons[i] ); 18305 } 18306 } 18307 18308 return conf; 18309 }, 18310 18311 /** 18312 * Display (and replace if there is an existing one) a popover attached to a button 18313 * @param {string|node} content Content to show 18314 * @param {DataTable.Api} hostButton DT API instance of the button 18315 * @param {object} inOpts Options (see object below for all options) 18316 */ 18317 _popover: function ( content, hostButton, inOpts, e ) { 18318 var dt = hostButton; 18319 var buttonsSettings = this.c; 18320 var closed = false; 18321 var options = $.extend( { 18322 align: 'button-left', // button-right, dt-container, split-left, split-right 18323 autoClose: false, 18324 background: true, 18325 backgroundClassName: 'dt-button-background', 18326 closeButton: true,
18327 contentClassName: buttonsSettings.dom.collection.className, 18328 collectionLayout: '', 18329 collectionTitle: '', 18330 dropup: false, 18331 fade: 400, 18332 popoverTitle: '', 18333 rightAlignClassName: 'dt-button-right', 18334 tag: buttonsSettings.dom.collection.tag 18335 }, inOpts ); 18336 18337 var hostNode = hostButton.node(); 18338 18339 var close = function () { 18340 closed = true; 18341 18342 _fadeOut( 18343 $('.dt-button-collection'), 18344 options.fade, 18345 function () { 18346 $(this).detach(); 18347 } 18348 ); 18349 18350 $(dt.buttons( '[aria-haspopup="dialog"][aria-expanded="true"]' ).nodes()) 18351 .attr('aria-expanded', 'false'); 18352 18353 $('div.dt-button-background').off( 'click.dtb-collection' ); 18354 Buttons.background( false, options.backgroundClassName, options.fade, hostNode ); 18355 18356 $(window).off('resize.resize.dtb-collection'); 18357 $('body').off( '.dtb-collection' ); 18358 dt.off( 'buttons-action.b-internal' ); 18359 dt.off( 'destroy' ); 18360 }; 18361 18362 if (content === false) { 18363 close(); 18364 return; 18365 } 18366 18367 var existingExpanded = $(dt.buttons( '[aria-haspopup="dialog"][aria-expanded="true"]' ).nodes()); 18368 if ( existingExpanded.length ) { 18369 // Reuse the current position if the button that was triggered is inside an existing collection 18370 if (hostNode.closest('div.dt-button-collection').length) { 18371 hostNode = existingExpanded.eq(0); 18372 } 18373 18374 close(); 18375 } 18376 18377 // Try to be smart about the layout 18378 var cnt = $('.dt-button', content).length; 18379 var mod = ''; 18380 18381 if (cnt === 3) { 18382 mod = 'dtb-b3'; 18383 } 18384 else if (cnt === 2) { 18385 mod = 'dtb-b2'; 18386 } 18387 else if (cnt === 1) { 18388 mod = 'dtb-b1'; 18389 } 18390 18391 var display = $('<div/>') 18392 .addClass('dt-button-collection') 18393 .addClass(options.collectionLayout) 18394 .addClass(options.splitAlignClass) 18395 .addClass(mod) 18396 .css('display', 'none') 18397 .attr({
18398 'aria-modal': true, 18399 role: 'dialog' 18400 }); 18401 18402 content = $(content) 18403 .addClass(options.contentClassName) 18404 .attr('role', 'menu') 18405 .appendTo(display); 18406 18407 hostNode.attr( 'aria-expanded', 'true' ); 18408 18409 if ( hostNode.parents('body')[0] !== document.body ) { 18410 hostNode = document.body.lastChild; 18411 } 18412 18413 if ( options.popoverTitle ) { 18414 display.prepend('<div class="dt-button-collection-title">'+options.popoverTitle+'</div>'); 18415 } 18416 else if ( options.collectionTitle ) { 18417 display.prepend('<div class="dt-button-collection-title">'+options.collectionTitle+'</div>'); 18418 } 18419 18420 if (options.closeButton) { 18421 display.prepend('<div class="dtb-popover-close">x</div>').addClass('dtb-collection-closeable') 18422 } 18423 18424 _fadeIn( display.insertAfter( hostNode ), options.fade ); 18425 18426 var tableContainer = $( hostButton.table().container() ); 18427 var position = display.css( 'position' ); 18428 18429 if ( options.span === 'container' || options.align === 'dt-container' ) { 18430 hostNode = hostNode.parent(); 18431 display.css('width', tableContainer.width()); 18432 } 18433 18434 // Align the popover relative to the DataTables container 18435 // Useful for wide popovers such as SearchPanes 18436 if (position === 'absolute') { 18437 // Align relative to the host button 18438 var offsetParent = $(hostNode[0].offsetParent); 18439 var buttonPosition = hostNode.position(); 18440 var buttonOffset = hostNode.offset(); 18441 var tableSizes = offsetParent.offset(); 18442 var containerPosition = offsetParent.position(); 18443 var computed = window.getComputedStyle(offsetParent[0]); 18444 18445 tableSizes.height = offsetParent.outerHeight(); 18446 tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft); 18447 tableSizes.right = tableSizes.left + tableSizes.width; 18448 tableSizes.bottom = tableSizes.top + tableSizes.height; 18449 18450 // Set the initial position so we can read height / width 18451 var top = buttonPosition.top + hostNode.outerHeight(); 18452 var left = buttonPosition.left; 18453 18454 display.css( { 18455 top: top, 18456 left: left 18457 } ); 18458 18459 // Get the popover position 18460 computed = window.getComputedStyle(display[0]); 18461 var popoverSizes = display.offset(); 18462 18463 popoverSizes.height = display.outerHeight(); 18464 popoverSizes.width = display.outerWidth(); 18465 popoverSizes.right = popoverSizes.left + popoverSizes.width; 18466 popoverSizes.bottom = popoverSizes.top + popoverSizes.height; 18467 popoverSizes.marginTop = parseFloat(computed.marginTop); 18468 popoverSizes.marginBottom = parseFloat(computed.marginBottom); 18469 18470 // First position per the class requirements - pop up and right align 18471 if (options.dropup) { 18472 top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; 18473 } 18474 18475 if (options.align === 'button-right' || display.hasClass( options.rightAlignClassName )) { 18476 left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth(); 18477 } 18478 18479 // Container alignment - make sure it doesn't overflow the table container 18480 if (options.align === 'dt-container' || options.align === 'container') { 18481 if (left < buttonPosition.left) { 18482 left = -buttonPosition.left; 18483 } 18484 18485 if (left + popoverSizes.width > tableSizes.width) { 18486 left = tableSizes.width - popoverSizes.width; 18487 } 18488 } 18489 18490 // Window adjustment 18491 if (containerPosition.left + left + popoverSizes.width > $(window).width()) { 18492 // Overflowing the document to the right 18493 left = $(window).width() - popoverSizes.width - containerPosition.left; 18494 } 18495 18496 if (buttonOffset.left + left < 0) { 18497 // Off to the left of the document 18498 left = -buttonOffset.left; 18499 } 18500 18501 if (containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop()) { 18502 // Pop up if otherwise we'd need the user to scroll down 18503 top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; 18504 } 18505 18506 if (containerPosition.top + top < $(window).scrollTop()) { 18507 // Correction for when the top is beyond the top of the page 18508 top = buttonPosition.top + hostNode.outerHeight(); 18509 } 18510 18511 // Calculations all done - now set it 18512 display.css( { 18513 top: top, 18514 left: left 18515 } ); 18516 } 18517 else { 18518 // Fix position - centre on screen 18519 var position = function () { 18520 var half = $(window).height() / 2; 18521 18522 var top = display.height() / 2; 18523 if ( top > half ) { 18524 top = half; 18525 } 18526 18527 display.css( 'marginTop', top*-1 ); 18528 }; 18529 18530 position(); 18531 18532 $(window).on('resize.dtb-collection', function () { 18533 position(); 18534 }); 18535 } 18536 18537 if ( options.background ) { 18538 Buttons.background( 18539 true, 18540 options.backgroundClassName, 18541 options.fade, 18542 options.backgroundHost || hostNode 18543 ); 18544 } 18545 18546 // This is bonkers, but if we don't have a click listener on the 18547 // background element, iOS Safari will ignore the body click 18548 // listener below. An empty function here is all that is 18549 // required to make it work... 18550 $('div.dt-button-background').on( 'click.dtb-collection', function () {} ); 18551 18552 if ( options.autoClose ) { 18553 setTimeout( function () { 18554 dt.on( 'buttons-action.b-internal', function (e, btn, dt, node) { 18555 if ( node[0] === hostNode[0] ) { 18556 return; 18557 } 18558 close(); 18559 } ); 18560 }, 0); 18561 } 18562 18563 $(display).trigger('buttons-popover.dt'); 18564 18565 18566 dt.on('destroy', close); 18567 18568 setTimeout(function() { 18569 closed = false; 18570 $('body') 18571 .on( 'click.dtb-collection', function (e) { 18572 if (closed) { 18573 return; 18574 } 18575
18576 // andSelf is deprecated in jQ1.8, but we want 1.7 compat 18577 var back = $.fn.addBack ? 'addBack' : 'andSelf'; 18578 var parent = $(e.target).parent()[0]; 18579 18580 if (( ! $(e.target).parents()[back]().filter( content ).length && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background')) { 18581 close(); 18582 } 18583 } ) 18584 .on( 'keyup.dtb-collection', function (e) { 18585 if ( e.keyCode === 27 ) { 18586 close(); 18587 } 18588 } ) 18589 .on( 'keydown.dtb-collection', function (e) { 18590 // Focus trap for tab key 18591 var elements = $('a, button', content); 18592 var active = document.activeElement; 18593 18594 if (e.keyCode !== 9) { // tab 18595 return; 18596 } 18597 18598 if (elements.index(active) === -1) { 18599 // If current focus is not inside the popover 18600 elements.first().focus(); 18601 e.preventDefault(); 18602 } 18603 else if (e.shiftKey) { 18604 // Reverse tabbing order when shift key is pressed 18605 if (active === elements[0]) { 18606 elements.last().focus(); 18607 e.preventDefault(); 18608 } 18609 } 18610 else { 18611 if (active === elements.last()[0]) { 18612 elements.first().focus(); 18613 e.preventDefault(); 18614 } 18615 } 18616 } ); 18617 }, 0); 18618 } 18619} ); 18620 18621 18622 18623/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 18624 * Statics 18625 */ 18626 18627/** 18628 * Show / hide a background layer behind a collection 18629 * @param {boolean} Flag to indicate if the background should be shown or 18630 * hidden 18631 * @param {string} Class to assign to the background 18632 * @static 18633 */ 18634Buttons.background = function ( show, className, fade, insertPoint ) { 18635 if ( fade === undefined ) { 18636 fade = 400; 18637 } 18638 if ( ! insertPoint ) { 18639 insertPoint = document.body; 18640 } 18641 18642 if ( show ) { 18643 _fadeIn( 18644 $('<div/>') 18645 .addClass( className ) 18646 .css( 'display', 'none' ) 18647 .insertAfter( insertPoint ), 18648 fade 18649 ); 18650 } 18651 else { 18652 _fadeOut( 18653 $('div.'+className), 18654 fade, 18655 function () { 18656 $(this) 18657 .removeClass( className ) 18658 .remove(); 18659 } 18660 ); 18661 } 18662}; 18663 18664/** 18665 * Instance selector - select Buttons instances based on an instance selector 18666 * value from the buttons assigned to a DataTable. This is only useful if 18667 * multiple instances are attached to a DataTable. 18668 * @param {string|int|array} Instance selector - see `instance-selector` 18669 * documentation on the DataTables site 18670 * @param {array} Button instance array that was attached to the DataTables 18671 * settings object 18672 * @return {array} Buttons instances 18673 * @static 18674 */ 18675Buttons.instanceSelector = function ( group, buttons ) 18676{ 18677 if ( group === undefined || group === null ) { 18678 return $.map( buttons, function ( v ) { 18679 return v.inst; 18680 } ); 18681 } 18682 18683 var ret = []; 18684 var names = $.map( buttons, function ( v ) { 18685 return v.name; 18686 } ); 18687 18688 // Flatten the group selector into an array of single options 18689 var process = function ( input ) { 18690 if ( Array.isArray( input ) ) { 18691 for ( var i=0, ien=input.length ; i<ien ; i++ ) { 18692 process( input[i] ); 18693 } 18694 return; 18695 } 18696 18697 if ( typeof input === 'string' ) { 18698 if ( input.indexOf( ',' ) !== -1 ) { 18699 // String selector, list of names 18700 process( input.split(',') ); 18701 } 18702 else { 18703 // String selector individual name 18704 var idx = $.inArray( input.trim(), names ); 18705 18706 if ( idx !== -1 ) { 18707 ret.push( buttons[ idx ].inst ); 18708 } 18709 } 18710 } 18711 else if ( typeof input === 'number' ) { 18712 // Index selector 18713 ret.push( buttons[ input ].inst ); 18714 } 18715 else if ( typeof input === 'object' ) { 18716 // Actual instance selector 18717 ret.push( input ); 18718 } 18719 }; 18720 18721 process( group ); 18722 18723 return ret; 18724}; 18725 18726/** 18727 * Button selector - select one or more buttons from a selector input so some 18728 * operation can be performed on them. 18729 * @param {array} Button instances array that the selector should operate on 18730 * @param {string|int|node|jQuery|array} Button selector - see 18731 * `button-selector` documentation on the DataTables site 18732 * @return {array} Array of objects containing `inst` and `idx` properties of
18733 * the selected buttons so you know which instance each button belongs to. 18734 * @static 18735 */ 18736Buttons.buttonSelector = function ( insts, selector ) 18737{ 18738 var ret = []; 18739 var nodeBuilder = function ( a, buttons, baseIdx ) { 18740 var button; 18741 var idx; 18742 18743 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { 18744 button = buttons[i]; 18745 18746 if ( button ) { 18747 idx = baseIdx !== undefined ? 18748 baseIdx+i : 18749 i+''; 18750 18751 a.push( { 18752 node: button.node, 18753 name: button.conf.name, 18754 idx: idx 18755 } ); 18756 18757 if ( button.buttons ) { 18758 nodeBuilder( a, button.buttons, idx+'-' ); 18759 } 18760 } 18761 } 18762 }; 18763 18764 var run = function ( selector, inst ) { 18765 var i, ien; 18766 var buttons = []; 18767 nodeBuilder( buttons, inst.s.buttons ); 18768 18769 var nodes = $.map( buttons, function (v) { 18770 return v.node; 18771 } ); 18772 18773 if ( Array.isArray( selector ) || selector instanceof $ ) { 18774 for ( i=0, ien=selector.length ; i<ien ; i++ ) { 18775 run( selector[i], inst ); 18776 } 18777 return; 18778 } 18779 18780 if ( selector === null || selector === undefined || selector === '*' ) { 18781 // Select all 18782 for ( i=0, ien=buttons.length ; i<ien ; i++ ) { 18783 ret.push( { 18784 inst: inst, 18785 node: buttons[i].node 18786 } ); 18787 } 18788 } 18789 else if ( typeof selector === 'number' ) { 18790 // Main button index selector 18791 if (inst.s.buttons[ selector ]) { 18792 ret.push( { 18793 inst: inst, 18794 node: inst.s.buttons[ selector ].node 18795 } ); 18796 } 18797 } 18798 else if ( typeof selector === 'string' ) { 18799 if ( selector.indexOf( ',' ) !== -1 ) { 18800 // Split 18801 var a = selector.split(','); 18802 18803 for ( i=0, ien=a.length ; i<ien ; i++ ) { 18804 run( a[i].trim(), inst ); 18805 } 18806 } 18807 else if ( selector.match( /^\d+(\-\d+)*$/ ) ) { 18808 // Sub-button index selector 18809 var indexes = $.map( buttons, function (v) { 18810 return v.idx; 18811 } ); 18812 18813 ret.push( { 18814 inst: inst, 18815 node: buttons[ $.inArray( selector, indexes ) ].node 18816 } ); 18817 } 18818 else if ( selector.indexOf( ':name' ) !== -1 ) { 18819 // Button name selector 18820 var name = selector.replace( ':name', '' ); 18821 18822 for ( i=0, ien=buttons.length ; i<ien ; i++ ) { 18823 if ( buttons[i].name === name ) { 18824 ret.push( { 18825 inst: inst, 18826 node: buttons[i].node 18827 } ); 18828 } 18829 } 18830 } 18831 else { 18832 // jQuery selector on the nodes 18833 $( nodes ).filter( selector ).each( function () { 18834 ret.push( { 18835 inst: inst, 18836 node: this 18837 } ); 18838 } ); 18839 } 18840 } 18841 else if ( typeof selector === 'object' && selector.nodeName ) { 18842 // Node selector 18843 var idx = $.inArray( selector, nodes ); 18844 18845 if ( idx !== -1 ) { 18846 ret.push( { 18847 inst: inst, 18848 node: nodes[ idx ] 18849 } ); 18850 } 18851 } 18852 }; 18853 18854 18855 for ( var i=0, ien=insts.length ; i<ien ; i++ ) { 18856 var inst = insts[i]; 18857 18858 run( selector, inst ); 18859 } 18860 18861 return ret; 18862}; 18863 18864/** 18865 * Default function used for formatting output data. 18866 * @param {*} str Data to strip 18867 */ 18868Buttons.stripData = function ( str, config ) { 18869 if ( typeof str !== 'string' ) { 18870 return str; 18871 } 18872 18873 // Always remove script tags 18874 str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' ); 18875 18876 // Always remove comments 18877 str = str.replace( /<!\-\-.*?\-\->/g, '' ); 18878 18879 if ( ! config || config.stripHtml ) { 18880 str = str.replace( /<[^>]*>/g, '' ); 18881 } 18882 18883 if ( ! config || config.trim ) { 18884 str = str.replace( /^\s+|\s+$/g, '' ); 18885 } 18886 18887 if ( ! config || config.stripNewlines ) { 18888 str = str.replace( /\n/g, ' ' ); 18889 } 18890 18891 if ( ! config || config.decodeEntities ) { 18892 _exportTextarea.innerHTML = str; 18893 str = _exportTextarea.value; 18894 } 18895 18896 return str; 18897}; 18898 18899 18900/** 18901 * Buttons defaults. For full documentation, please refer to the docs/option 18902 * directory or the DataTables site. 18903 * @type {Object} 18904 * @static 18905 */ 18906Buttons.defaults = { 18907 buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ], 18908 name: 'main', 18909 tabIndex: 0, 18910 dom: { 18911 container: { 18912 tag: 'div', 18913 className: 'dt-buttons' 18914 }, 18915 collection: { 18916 tag: 'div', 18917 className: '' 18918 }, 18919 button: { 18920 tag: 'button', 18921 className: 'dt-button', 18922 active: 'active', 18923 disabled: 'disabled', 18924 spacerClass: '' 18925 }, 18926 buttonLiner: { 18927 tag: 'span', 18928 className: '' 18929 }, 18930 split: { 18931 tag: 'div', 18932 className: 'dt-button-split', 18933 }, 18934 splitWrapper: { 18935 tag: 'div', 18936 className: 'dt-btn-split-wrapper', 18937 }, 18938 splitDropdown: { 18939 tag: 'button', 18940 text: '▼', 18941 className: 'dt-btn-split-drop', 18942 align: 'split-right', 18943 splitAlignClass: 'dt-button-split-left' 18944 }, 18945 splitDropdownButton: { 18946 tag: 'button', 18947 className: 'dt-btn-split-drop-button dt-button', 18948 }, 18949 splitCollection: { 18950 tag: 'div', 18951 className: 'dt-button-split-collection', 18952 } 18953 } 18954}; 18955 18956/** 18957 * Version information 18958 * @type {string} 18959 * @static 18960 */ 18961Buttons.version = '2.3.6'; 18962 18963 18964$.extend( _dtButtons, { 18965 collection: { 18966 text: function ( dt ) { 18967 return dt.i18n( 'buttons.collection', 'Collection' ); 18968 }, 18969 className: 'buttons-collection',
18970 closeButton: false, 18971 init: function ( dt, button, config ) { 18972 button.attr( 'aria-expanded', false ); 18973 }, 18974 action: function ( e, dt, button, config ) { 18975 if ( config._collection.parents('body').length ) { 18976 this.popover(false, config); 18977 } 18978 else { 18979 this.popover(config._collection, config); 18980 } 18981 18982 // When activated using a key - auto focus on the 18983 // first item in the popover 18984 if (e.type === 'keypress') { 18985 $('a, button', config._collection).eq(0).focus(); 18986 } 18987 }, 18988 attr: { 18989 'aria-haspopup': 'dialog' 18990 } 18991 // Also the popover options, defined in Buttons.popover 18992 }, 18993 split: { 18994 text: function ( dt ) { 18995 return dt.i18n( 'buttons.split', 'Split' ); 18996 }, 18997 className: 'buttons-split', 18998 closeButton: false, 18999 init: function ( dt, button, config ) { 19000 return button.attr( 'aria-expanded', false ); 19001 }, 19002 action: function ( e, dt, button, config ) { 19003 this.popover(config._collection, config); 19004 }, 19005 attr: { 19006 'aria-haspopup': 'dialog' 19007 } 19008 // Also the popover options, defined in Buttons.popover 19009 }, 19010 copy: function ( dt, conf ) { 19011 if ( _dtButtons.copyHtml5 ) { 19012 return 'copyHtml5'; 19013 } 19014 }, 19015 csv: function ( dt, conf ) { 19016 if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) { 19017 return 'csvHtml5'; 19018 } 19019 }, 19020 excel: function ( dt, conf ) { 19021 if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) { 19022 return 'excelHtml5'; 19023 } 19024 }, 19025 pdf: function ( dt, conf ) { 19026 if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) { 19027 return 'pdfHtml5'; 19028 } 19029 }, 19030 pageLength: function ( dt ) { 19031 var lengthMenu = dt.settings()[0].aLengthMenu; 19032 var vals = []; 19033 var lang = []; 19034 var text = function ( dt ) { 19035 return dt.i18n( 'buttons.pageLength', { 19036 "-1": 'Show all rows', 19037 _: 'Show %d rows' 19038 }, dt.page.len() ); 19039 }; 19040 19041 // Support for DataTables 1.x 2D array 19042 if (Array.isArray( lengthMenu[0] )) { 19043 vals = lengthMenu[0]; 19044 lang = lengthMenu[1]; 19045 } 19046 else { 19047 for (var i=0 ; i<lengthMenu.length ; i++) { 19048 var option = lengthMenu[i]; 19049 19050 // Support for DataTables 2 object in the array 19051 if ($.isPlainObject(option)) { 19052 vals.push(option.value); 19053 lang.push(option.label); 19054 } 19055 else { 19056 vals.push(option); 19057 lang.push(option); 19058 } 19059 } 19060 } 19061 19062 return { 19063 extend: 'collection', 19064 text: text, 19065 className: 'buttons-page-length', 19066 autoClose: true, 19067 buttons: $.map( vals, function ( val, i ) { 19068 return { 19069 text: lang[i], 19070 className: 'button-page-length', 19071 action: function ( e, dt ) { 19072 dt.page.len( val ).draw(); 19073 }, 19074 init: function ( dt, node, conf ) { 19075 var that = this; 19076 var fn = function () { 19077 that.active( dt.page.len() === val ); 19078 }; 19079 19080 dt.on( 'length.dt'+conf.namespace, fn ); 19081 fn(); 19082 }, 19083 destroy: function ( dt, node, conf ) { 19084 dt.off( 'length.dt'+conf.namespace ); 19085 } 19086 }; 19087 } ), 19088 init: function ( dt, node, conf ) { 19089 var that = this; 19090 dt.on( 'length.dt'+conf.namespace, function () { 19091 that.text( conf.text ); 19092 } ); 19093 }, 19094 destroy: function ( dt, node, conf ) { 19095 dt.off( 'length.dt'+conf.namespace ); 19096 } 19097 }; 19098 }, 19099 spacer: { 19100 style: 'empty', 19101 spacer: true, 19102 text: function ( dt ) { 19103 return dt.i18n( 'buttons.spacer', '' ); 19104 } 19105 } 19106} ); 19107 19108 19109/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 19110 * DataTables API 19111 * 19112 * For complete documentation, please refer to the docs/api directory or the 19113 * DataTables site 19114 */ 19115 19116// Buttons group and individual button selector 19117DataTable.Api.register( 'buttons()', function ( group, selector ) { 19118 // Argument shifting 19119 if ( selector === undefined ) { 19120 selector = group; 19121 group = undefined; 19122 } 19123 19124 this.selector.buttonGroup = group; 19125 19126 var res = this.iterator( true, 'table', function ( ctx ) { 19127 if ( ctx._buttons ) { 19128 return Buttons.buttonSelector( 19129 Buttons.instanceSelector( group, ctx._buttons ), 19130 selector 19131 ); 19132 } 19133 }, true ); 19134 19135 res._groupSelector = group; 19136 return res; 19137} ); 19138 19139// Individual button selector 19140DataTable.Api.register( 'button()', function ( group, selector ) { 19141 // just run buttons() and truncate 19142 var buttons = this.buttons( group, selector ); 19143 19144 if ( buttons.length > 1 ) { 19145 buttons.splice( 1, buttons.length ); 19146 } 19147 19148 return buttons; 19149} ); 19150 19151// Active buttons 19152DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) { 19153 if ( flag === undefined ) { 19154 return this.map( function ( set ) { 19155 return set.inst.active( set.node ); 19156 } ); 19157 } 19158 19159 return this.each( function ( set ) { 19160 set.inst.active( set.node, flag ); 19161 } ); 19162} ); 19163 19164// Get / set button action 19165DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) { 19166 if ( action === undefined ) { 19167 return this.map( function ( set ) { 19168 return set.inst.action( set.node ); 19169 } ); 19170 } 19171 19172 return this.each( function ( set ) { 19173 set.inst.action( set.node, action ); 19174 } ); 19175} ); 19176 19177// Collection control 19178DataTable.Api.registerPlural( 'buttons().collectionRebuild()', 'button().collectionRebuild()', function ( buttons ) { 19179 return this.each( function ( set ) { 19180 for(var i = 0; i < buttons.length; i++) { 19181 if(typeof buttons[i] === 'object') { 19182 buttons[i].parentConf = set; 19183 } 19184 } 19185 set.inst.collectionRebuild( set.node, buttons ); 19186 } ); 19187} ); 19188 19189// Enable / disable buttons 19190DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) { 19191 return this.each( function ( set ) { 19192 set.inst.enable( set.node, flag ); 19193 } ); 19194} ); 19195 19196// Disable buttons 19197DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () { 19198 return this.each( function ( set ) { 19199 set.inst.disable( set.node ); 19200 } ); 19201} ); 19202 19203// Button index 19204DataTable.Api.register( 'button().index()', function () { 19205 var idx = null; 19206 19207 this.each( function ( set ) { 19208 var res = set.inst.index( set.node ); 19209 19210 if (res !== null) { 19211 idx = res; 19212 } 19213 } ); 19214 19215 return idx; 19216} ); 19217 19218// Get button nodes 19219DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () { 19220 var jq = $(); 19221 19222 // jQuery will automatically reduce duplicates to a single entry 19223 $( this.each( function ( set ) {
19224 jq = jq.add( set.inst.node( set.node ) ); 19225 } ) ); 19226 19227 return jq; 19228} ); 19229 19230// Get / set button processing state 19231DataTable.Api.registerPlural( 'buttons().processing()', 'button().processing()', function ( flag ) { 19232 if ( flag === undefined ) { 19233 return this.map( function ( set ) { 19234 return set.inst.processing( set.node ); 19235 } ); 19236 } 19237 19238 return this.each( function ( set ) { 19239 set.inst.processing( set.node, flag ); 19240 } ); 19241} ); 19242 19243// Get / set button text (i.e. the button labels) 19244DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) { 19245 if ( label === undefined ) { 19246 return this.map( function ( set ) { 19247 return set.inst.text( set.node ); 19248 } ); 19249 } 19250 19251 return this.each( function ( set ) { 19252 set.inst.text( set.node, label ); 19253 } ); 19254} ); 19255 19256// Trigger a button's action 19257DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () { 19258 return this.each( function ( set ) { 19259 set.inst.node( set.node ).trigger( 'click' ); 19260 } ); 19261} ); 19262 19263// Button resolver to the popover 19264DataTable.Api.register( 'button().popover()', function (content, options) { 19265 return this.map( function ( set ) { 19266 return set.inst._popover( content, this.button(this[0].node), options ); 19267 } ); 19268} ); 19269 19270// Get the container elements 19271DataTable.Api.register( 'buttons().containers()', function () { 19272 var jq = $(); 19273 var groupSelector = this._groupSelector; 19274 19275 // We need to use the group selector directly, since if there are no buttons 19276 // the result set will be empty 19277 this.iterator( true, 'table', function ( ctx ) { 19278 if ( ctx._buttons ) { 19279 var insts = Buttons.instanceSelector( groupSelector, ctx._buttons ); 19280 19281 for ( var i=0, ien=insts.length ; i<ien ; i++ ) { 19282 jq = jq.add( insts[i].container() ); 19283 } 19284 } 19285 } ); 19286 19287 return jq; 19288} ); 19289
19290DataTable.Api.register( 'buttons().container()', function () { 19291 // API level of nesting is `buttons()` so we can zip into the containers method 19292 return this.containers().eq(0); 19293} ); 19294 19295// Add a new button 19296DataTable.Api.register( 'button().add()', function ( idx, conf, draw ) { 19297 var ctx = this.context; 19298 19299 // Don't use `this` as it could be empty - select the instances directly 19300 if ( ctx.length ) { 19301 var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons ); 19302 19303 if ( inst.length ) { 19304 inst[0].add( conf, idx , draw); 19305 } 19306 } 19307 19308 return this.button( this._groupSelector, idx ); 19309} ); 19310 19311// Destroy the button sets selected 19312DataTable.Api.register( 'buttons().destroy()', function () { 19313 this.pluck( 'inst' ).unique().each( function ( inst ) { 19314 inst.destroy(); 19315 } ); 19316 19317 return this; 19318} ); 19319 19320// Remove a button 19321DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () { 19322 this.each( function ( set ) { 19323 set.inst.remove( set.node ); 19324 } ); 19325 19326 return this; 19327} ); 19328 19329// Information box that can be used by buttons 19330var _infoTimer; 19331DataTable.Api.register( 'buttons.info()', function ( title, message, time ) { 19332 var that = this; 19333 19334 if ( title === false ) { 19335 this.off('destroy.btn-info'); 19336 _fadeOut( 19337 $('#datatables_buttons_info'), 19338 400, 19339 function () { 19340 $(this).remove(); 19341 } 19342 ); 19343 clearTimeout( _infoTimer ); 19344 _infoTimer = null; 19345 19346 return this; 19347 } 19348 19349 if ( _infoTimer ) { 19350 clearTimeout( _infoTimer ); 19351 } 19352 19353 if ( $('#datatables_buttons_info').length ) { 19354 $('#datatables_buttons_info').remove(); 19355 } 19356 19357 title = title ? '<h2>'+title+'</h2>' : ''; 19358 19359 _fadeIn( 19360 $('<div id="datatables_buttons_info" class="dt-button-info"/>') 19361 .html( title ) 19362 .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) ) 19363 .css( 'display', 'none' ) 19364 .appendTo( 'body' ) 19365 ); 19366 19367 if ( time !== undefined && time !== 0 ) { 19368 _infoTimer = setTimeout( function () { 19369 that.buttons.info( false ); 19370 }, time ); 19371 } 19372 19373 this.on('destroy.btn-info', function () { 19374 that.buttons.info(false); 19375 }); 19376 19377 return this; 19378} ); 19379 19380// Get data from the table for export - this is common to a number of plug-in 19381// buttons so it is included in the Buttons core library 19382DataTable.Api.register( 'buttons.exportData()', function ( options ) { 19383 if ( this.context.length ) { 19384 return _exportData( new DataTable.Api( this.context[0] ), options ); 19385 } 19386} ); 19387 19388// Get information about the export that is common to many of the export data 19389// types (DRY) 19390DataTable.Api.register( 'buttons.exportInfo()', function ( conf ) { 19391 if ( ! conf ) { 19392 conf = {}; 19393 } 19394 19395 return { 19396 filename: _filename( conf ), 19397 title: _title( conf ), 19398 messageTop: _message(this, conf.message || conf.messageTop, 'top'), 19399 messageBottom: _message(this, conf.messageBottom, 'bottom') 19400 }; 19401} ); 19402 19403 19404 19405/** 19406 * Get the file name for an exported file. 19407 * 19408 * @param {object} config Button configuration 19409 * @param {boolean} incExtension Include the file name extension 19410 */ 19411var _filename = function ( config ) 19412{ 19413 // Backwards compatibility 19414 var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ? 19415 config.title : 19416 config.filename; 19417 19418 if ( typeof filename === 'function' ) { 19419 filename = filename(); 19420 } 19421 19422 if ( filename === undefined || filename === null ) { 19423 return null; 19424 } 19425 19426 if ( filename.indexOf( '*' ) !== -1 ) { 19427 filename = filename.replace( '*', $('head > title').text() ).trim(); 19428 } 19429 19430 // Strip characters which the OS will object to 19431 filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); 19432 19433 var extension = _stringOrFunction( config.extension ); 19434 if ( ! extension ) { 19435 extension = ''; 19436 } 19437 19438 return filename + extension; 19439}; 19440 19441/** 19442 * Simply utility method to allow parameters to be given as a function 19443 * 19444 * @param {undefined|string|function} option Option 19445 * @return {null|string} Resolved value 19446 */ 19447var _stringOrFunction = function ( option ) 19448{ 19449 if ( option === null || option === undefined ) { 19450 return null; 19451 } 19452 else if ( typeof option === 'function' ) { 19453 return option(); 19454 } 19455 return option; 19456}; 19457 19458/** 19459 * Get the title for an exported file. 19460 * 19461 * @param {object} config Button configuration 19462 */ 19463var _title = function ( config ) 19464{ 19465 var title = _stringOrFunction( config.title ); 19466 19467 return title === null ? 19468 null : title.indexOf( '*' ) !== -1 ? 19469 title.replace( '*', $('head > title').text() || 'Exported data' ) : 19470 title; 19471}; 19472 19473var _message = function ( dt, option, position ) 19474{ 19475 var message = _stringOrFunction( option ); 19476 if ( message === null ) { 19477 return null; 19478 } 19479 19480 var caption = $('caption', dt.table().container()).eq(0); 19481 if ( message === '*' ) { 19482 var side = caption.css( 'caption-side' ); 19483 if ( side !== position ) { 19484 return null; 19485 } 19486 19487 return caption.length ? 19488 caption.text() : 19489 ''; 19490 } 19491 19492 return message; 19493}; 19494 19495 19496 19497 19498var _exportTextarea = $('<textarea/>')[0]; 19499var _exportData = function ( dt, inOpts ) 19500{ 19501 var config = $.extend( true, {}, { 19502 rows: null, 19503 columns: '', 19504 modifier: { 19505 search: 'applied', 19506 order: 'applied' 19507 }, 19508 orthogonal: 'display', 19509 stripHtml: true, 19510 stripNewlines: true, 19511 decodeEntities: true, 19512 trim: true, 19513 format: { 19514 header: function ( d ) {
19515 return Buttons.stripData( d, config ); 19516 }, 19517 footer: function ( d ) { 19518 return Buttons.stripData( d, config ); 19519 }, 19520 body: function ( d ) { 19521 return Buttons.stripData( d, config ); 19522 } 19523 }, 19524 customizeData: null 19525 }, inOpts ); 19526 19527 var header = dt.columns( config.columns ).indexes().map( function (idx) { 19528 var el = dt.column( idx ).header(); 19529 return config.format.header( el.innerHTML, idx, el ); 19530 } ).toArray(); 19531 19532 var footer = dt.table().footer() ? 19533 dt.columns( config.columns ).indexes().map( function (idx) { 19534 var el = dt.column( idx ).footer(); 19535 return config.format.footer( el ? el.innerHTML : '', idx, el ); 19536 } ).toArray() : 19537 null; 19538 19539 // If Select is available on this table, and any rows are selected, limit the export 19540 // to the selected rows. If no rows are selected, all rows will be exported. Specify 19541 // a `selected` modifier to control directly. 19542 var modifier = $.extend( {}, config.modifier ); 19543 if ( dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined ) { 19544 if ( dt.rows( config.rows, $.extend( { selected: true }, modifier ) ).any() ) { 19545 $.extend( modifier, { selected: true } ) 19546 } 19547 } 19548 19549 var rowIndexes = dt.rows( config.rows, modifier ).indexes().toArray(); 19550 var selectedCells = dt.cells( rowIndexes, config.columns ); 19551 var cells = selectedCells 19552 .render( config.orthogonal ) 19553 .toArray(); 19554 var cellNodes = selectedCells 19555 .nodes() 19556 .toArray(); 19557 19558 var columns = header.length; 19559 var rows = columns > 0 ? cells.length / columns : 0; 19560 var body = []; 19561 var cellCounter = 0; 19562 19563 for ( var i=0, ien=rows ; i<ien ; i++ ) { 19564 var row = [ columns ]; 19565 19566 for ( var j=0 ; j<columns ; j++ ) { 19567 row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] ); 19568 cellCounter++; 19569 } 19570 19571 body[i] = row; 19572 } 19573 19574 var data = { 19575 header: header, 19576 footer: footer, 19577 body: body 19578 }; 19579 19580 if ( config.customizeData ) { 19581 config.customizeData( data ); 19582 } 19583 19584 return data; 19585}; 19586 19587 19588/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 19589 * DataTables interface 19590 */ 19591 19592// Attach to DataTables objects for global access 19593$.fn.dataTable.Buttons = Buttons; 19594$.fn.DataTable.Buttons = Buttons; 19595 19596 19597 19598// DataTables creation - check if the buttons have been defined for this table, 19599// they will have been if the `B` option was used in `dom`, otherwise we should 19600// create the buttons instance here so they can be inserted into the document 19601// using the API. Listen for `init` for compatibility with pre 1.10.10, but to 19602// be removed in future. 19603$(document).on( 'init.dt plugin-init.dt', function (e, settings) { 19604 if ( e.namespace !== 'dt' ) { 19605 return; 19606 } 19607 19608 var opts = settings.oInit.buttons || DataTable.defaults.buttons; 19609 19610 if ( opts && ! settings._buttons ) { 19611 new Buttons( settings, opts ).container(); 19612 } 19613} ); 19614 19615function _init ( settings, options ) { 19616 var api = new DataTable.Api( settings ); 19617 var opts = options 19618 ? options 19619 : api.init().buttons || DataTable.defaults.buttons; 19620 19621 return new Buttons( api, opts ).container(); 19622} 19623 19624// DataTables `dom` feature option 19625DataTable.ext.feature.push( { 19626 fnInit: _init, 19627 cFeature: "B" 19628} ); 19629 19630// DataTables 2 layout feature 19631if ( DataTable.ext.features ) { 19632 DataTable.ext.features.register( 'buttons', _init ); 19633} 19634 19635 19636return DataTable; 19637})); 19638 19639 19640/*! DataTables styling wrapper for Buttons 19641 * ©2018 SpryMedia Ltd - datatables.net/license 19642 */ 19643 19644(function( factory ){ 19645 if ( typeof define === 'function' && define.amd ) { 19646 // AMD 19647 define( ['jquery', 'datatables.net-dt', 'datatables.net-buttons'], function ( $ ) { 19648 return factory( $, window, document ); 19649 } ); 19650 } 19651 else if ( typeof exports === 'object' ) { 19652 // CommonJS 19653 var jq = require('jquery'); 19654 var cjsRequires = function (root, $) { 19655 if ( ! $.fn.dataTable ) { 19656 require('datatables.net-dt')(root, $); 19657 } 19658 19659 if ( ! $.fn.dataTable.Buttons ) { 19660 require('datatables.net-buttons')(root, $); 19661 } 19662 }; 19663 19664 if (typeof window !== 'undefined') { 19665 module.exports = function (root, $) { 19666 if ( ! root ) { 19667 // CommonJS environments without a window global must pass a 19668 // root. This will give an error otherwise 19669 root = window; 19670 } 19671 19672 if ( ! $ ) { 19673 $ = jq( root ); 19674 } 19675 19676 cjsRequires( root, $ ); 19677 return factory( $, root, root.document ); 19678 }; 19679 } 19680 else { 19681 cjsRequires( window, jq ); 19682 module.exports = factory( jq, window, window.document ); 19683 } 19684 } 19685 else { 19686 // Browser 19687 factory( jQuery, window, document ); 19688 } 19689}(function( $, window, document, undefined ) { 19690'use strict'; 19691var DataTable = $.fn.dataTable; 19692 19693 19694 19695(function( factory ){ 19696 if ( typeof define === 'function' && define.amd ) { 19697 // AMD 19698 define( ['jquery', 'datatables.net-dt', 'datatables.net-buttons'], function ( $ ) { 19699 return factory( $, window, document ); 19700 } ); 19701 } 19702 else if ( typeof exports === 'object' ) { 19703 // CommonJS 19704 module.exports = function (root, $) { 19705 if ( ! root ) { 19706 root = window; 19707 } 19708 19709 if ( ! $ || ! $.fn.dataTable ) { 19710 $ = require('datatables.net-dt')(root, $).$; 19711 } 19712 19713 if ( ! $.fn.dataTable.Buttons ) { 19714 require('datatables.net-buttons')(root, $); 19715 } 19716 19717 return factory( $, root, root.document ); 19718 }; 19719 } 19720 else { 19721 // Browser 19722 factory( jQuery, window, document ); 19723 } 19724}(function( $, window, document, undefined ) { 19725 19726return $.fn.dataTable; 19727 19728})); 19729 19730return DataTable; 19731})); 19732 19733 19734/*! 19735 * Column visibility buttons for Buttons and DataTables. 19736 * 2016 SpryMedia Ltd - datatables.net/license 19737 */ 19738 19739(function( factory ){ 19740 if ( typeof define === 'function' && define.amd ) { 19741 // AMD 19742 define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) { 19743 return factory( $, window, document ); 19744 } ); 19745 } 19746 else if ( typeof exports === 'object' ) { 19747 // CommonJS 19748 var jq = require('jquery'); 19749 var cjsRequires = function (root, $) { 19750 if ( ! $.fn.dataTable ) { 19751 require('datatables.net')(root, $); 19752 } 19753 19754 if ( ! $.fn.dataTable.Buttons ) { 19755 require('datatables.net-buttons')(root, $); 19756 } 19757 }; 19758 19759 if (typeof window !== 'undefined') { 19760 module.exports = function (root, $) { 19761 if ( ! root ) { 19762 // CommonJS environments without a window global must pass a 19763 // root. This will give an error otherwise 19764 root = window; 19765 } 19766 19767 if ( ! $ ) { 19768 $ = jq( root ); 19769 } 19770 19771 cjsRequires( root, $ ); 19772 return factory( $, root, root.document ); 19773 }; 19774 } 19775 else { 19776 cjsRequires( window, jq ); 19777 module.exports = factory( jq, window, window.document ); 19778 } 19779 } 19780 else { 19781 // Browser 19782 factory( jQuery, window, document ); 19783 } 19784}(function( $, window, document, undefined ) { 19785'use strict'; 19786var DataTable = $.fn.dataTable; 19787 19788 19789 19790$.extend( DataTable.ext.buttons, { 19791 // A collection of column visibility buttons 19792 colvis: function ( dt, conf ) { 19793 var node = null; 19794 var buttonConf = { 19795 extend: 'collection', 19796 init: function ( dt, n ) { 19797 node = n; 19798 }, 19799 text: function ( dt ) { 19800 return dt.i18n( 'buttons.colvis', 'Column visibility' ); 19801 }, 19802 className: 'buttons-colvis',
19803 closeButton: false, 19804 buttons: [ { 19805 extend: 'columnsToggle', 19806 columns: conf.columns, 19807 columnText: conf.columnText 19808 } ] 19809 }; 19810 19811 // Rebuild the collection with the new column structure if columns are reordered 19812 dt.on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) { 19813 dt.button(null, dt.button(null, node).node()).collectionRebuild([{ 19814 extend: 'columnsToggle', 19815 columns: conf.columns, 19816 columnText: conf.columnText 19817 }]); 19818 }); 19819 19820 return buttonConf; 19821 }, 19822 19823 // Selected columns with individual buttons - toggle column visibility 19824 columnsToggle: function ( dt, conf ) { 19825 var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { 19826 return { 19827 extend: 'columnToggle', 19828 columns: idx, 19829 columnText: conf.columnText 19830 }; 19831 } ).toArray(); 19832 19833 return columns; 19834 }, 19835 19836 // Single button to toggle column visibility 19837 columnToggle: function ( dt, conf ) { 19838 return { 19839 extend: 'columnVisibility', 19840 columns: conf.columns, 19841 columnText: conf.columnText 19842 }; 19843 }, 19844 19845 // Selected columns with individual buttons - set column visibility 19846 columnsVisibility: function ( dt, conf ) { 19847 var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { 19848 return { 19849 extend: 'columnVisibility', 19850 columns: idx, 19851 visibility: conf.visibility, 19852 columnText: conf.columnText 19853 }; 19854 } ).toArray(); 19855 19856 return columns; 19857 }, 19858 19859 // Single button to set column visibility 19860 columnVisibility: { 19861 columns: undefined, // column selector 19862 text: function ( dt, button, conf ) { 19863 return conf._columnText( dt, conf ); 19864 }, 19865 className: 'buttons-columnVisibility', 19866 action: function ( e, dt, button, conf ) { 19867 var col = dt.columns( conf.columns ); 19868 var curr = col.visible(); 19869 19870 col.visible( conf.visibility !== undefined ? 19871 conf.visibility : 19872 ! (curr.length ? curr[0] : false ) 19873 ); 19874 }, 19875 init: function ( dt, button, conf ) { 19876 var that = this; 19877 button.attr( 'data-cv-idx', conf.columns ); 19878 19879 dt 19880 .on( 'column-visibility.dt'+conf.namespace, function (e, settings) { 19881 if ( ! settings.bDestroying && settings.nTable == dt.settings()[0].nTable ) { 19882 that.active( dt.column( conf.columns ).visible() ); 19883 } 19884 } ) 19885 .on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) { 19886 // Button has been removed from the DOM 19887 if ( conf.destroying ) { 19888 return; 19889 } 19890 19891 if ( dt.columns( conf.columns ).count() !== 1 ) { 19892 return; 19893 } 19894 19895 // This button controls the same column index but the text for the column has 19896 // changed 19897 that.text( conf._columnText( dt, conf ) ); 19898 19899 // Since its a different column, we need to check its visibility 19900 that.active( dt.column( conf.columns ).visible() ); 19901 } ); 19902 19903 this.active( dt.column( conf.columns ).visible() ); 19904 }, 19905 destroy: function ( dt, button, conf ) { 19906 dt 19907 .off( 'column-visibility.dt'+conf.namespace ) 19908 .off( 'column-reorder.dt'+conf.namespace ); 19909 }, 19910 19911 _columnText: function ( dt, conf ) { 19912 // Use DataTables' internal data structure until this is presented 19913 // is a public API. The other option is to use 19914 // `$( column(col).node() ).text()` but the node might not have been 19915 // populated when Buttons is constructed. 19916 var idx = dt.column( conf.columns ).index(); 19917 var title = dt.settings()[0].aoColumns[ idx ].sTitle; 19918 19919 if (! title) { 19920 title = dt.column(idx).header().innerHTML; 19921 } 19922 19923 title = title 19924 .replace(/\n/g," ") // remove new lines 19925 .replace(/<br\s*\/?>/gi, " ") // replace line breaks with spaces 19926 .replace(/<select(.*?)<\/select>/g, "") // remove select tags, including options text 19927 .replace(/<!\-\-.*?\-\->/g, "") // strip HTML comments 19928 .replace(/<.*?>/g, "") // strip HTML 19929 .replace(/^\s+|\s+$/g,""); // trim 19930 19931 return conf.columnText ? 19932 conf.columnText( dt, idx, title ) : 19933 title; 19934 } 19935 }, 19936 19937 19938 colvisRestore: { 19939 className: 'buttons-colvisRestore', 19940 19941 text: function ( dt ) { 19942 return dt.i18n( 'buttons.colvisRestore', 'Restore visibility' ); 19943 }, 19944 19945 init: function ( dt, button, conf ) { 19946 conf._visOriginal = dt.columns().indexes().map( function ( idx ) { 19947 return dt.column( idx ).visible(); 19948 } ).toArray(); 19949 }, 19950 19951 action: function ( e, dt, button, conf ) { 19952 dt.columns().every( function ( i ) { 19953 // Take into account that ColReorder might have disrupted our 19954 // indexes 19955 var idx = dt.colReorder && dt.colReorder.transpose ? 19956 dt.colReorder.transpose( i, 'toOriginal' ) : 19957 i; 19958 19959 this.visible( conf._visOriginal[ idx ] ); 19960 } ); 19961 } 19962 }, 19963 19964 19965 colvisGroup: { 19966 className: 'buttons-colvisGroup', 19967 19968 action: function ( e, dt, button, conf ) { 19969 dt.columns( conf.show ).visible( true, false ); 19970 dt.columns( conf.hide ).visible( false, false ); 19971 19972 dt.columns.adjust(); 19973 }, 19974 19975 show: [], 19976 19977 hide: [] 19978 } 19979} ); 19980 19981 19982return DataTable; 19983})); 19984 19985 19986/*! 19987 * HTML5 export buttons for Buttons and DataTables. 19988 * 2016 SpryMedia Ltd - datatables.net/license 19989 * 19990 * FileSaver.js (1.3.3) - MIT license 19991 * Copyright © 2016 Eli Grey - http://eligrey.com 19992 */ 19993 19994(function( factory ){ 19995 if ( typeof define === 'function' && define.amd ) { 19996 // AMD 19997 define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) { 19998 return factory( $, window, document ); 19999 } ); 20000 } 20001 else if ( typeof exports === 'object' ) { 20002 // CommonJS 20003 var jq = require('jquery'); 20004 var cjsRequires = function (root, $) { 20005 if ( ! $.fn.dataTable ) { 20006 require('datatables.net')(root, $); 20007 } 20008 20009 if ( ! $.fn.dataTable.Buttons ) { 20010 require('datatables.net-buttons')(root, $); 20011 } 20012 }; 20013 20014 if (typeof window !== 'undefined') { 20015 module.exports = function (root, $, jszip, pdfmake) { 20016 if ( ! root ) { 20017 // CommonJS environments without a window global must pass a 20018 // root. This will give an error otherwise 20019 root = window; 20020 } 20021 20022 if ( ! $ ) { 20023 $ = jq( root ); 20024 } 20025 20026 cjsRequires( root, $ ); 20027 return factory( $, root, root.document, jszip, pdfmake ); 20028 }; 20029 } 20030 else { 20031 cjsRequires( window, jq ); 20032 module.exports = factory( jq, window, window.document ); 20033 } 20034 } 20035 else { 20036 // Browser 20037 factory( jQuery, window, document ); 20038 } 20039}(function( $, window, document, jszip, pdfmake, undefined ) { 20040'use strict'; 20041var DataTable = $.fn.dataTable; 20042 20043 20044 20045// Allow the constructor to pass in JSZip and PDFMake from external requires. 20046// Otherwise, use globally defined variables, if they are available. 20047var useJszip; 20048var usePdfmake; 20049 20050function _jsZip () { 20051 return useJszip || window.JSZip; 20052} 20053function _pdfMake () { 20054 return usePdfmake || window.pdfMake; 20055} 20056 20057DataTable.Buttons.pdfMake = function (_) { 20058 if ( ! _ ) { 20059 return _pdfMake(); 20060 } 20061 usePdfmake = _; 20062} 20063 20064DataTable.Buttons.jszip = function (_) { 20065 if ( ! _ ) { 20066 return _jsZip(); 20067 } 20068 useJszip = _; 20069} 20070 20071 20072/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20073 * FileSaver.js dependency 20074 */ 20075 20076/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 20077 20078var _saveAs = (function(view) { 20079 "use strict"; 20080 // IE <10 is explicitly unsupported 20081 if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { 20082 return; 20083 } 20084 var 20085 doc = view.document 20086 // only get URL when necessary in case Blob.js hasn't overridden it yet 20087 , get_URL = function() { 20088 return view.URL || view.webkitURL || view; 20089 } 20090 , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 20091 , can_use_save_link = "download" in save_link 20092 , click = function(node) { 20093 var event = new MouseEvent("click"); 20094 node.dispatchEvent(event); 20095 } 20096 , is_safari = /constructor/i.test(view.HTMLElement) || view.safari 20097 , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) 20098 , throw_outside = function(ex) { 20099 (view.setImmediate || view.setTimeout)(function() { 20100 throw ex; 20101 }, 0); 20102 } 20103 , force_saveable_type = "application/octet-stream" 20104 // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to 20105 , arbitrary_revoke_timeout = 1000 * 40 // in ms 20106 , revoke = function(file) { 20107 var revoker = function() { 20108 if (typeof file === "string") { // file is an object URL 20109 get_URL().revokeObjectURL(file); 20110 } else { // file is a File 20111 file.remove(); 20112 } 20113 }; 20114 setTimeout(revoker, arbitrary_revoke_timeout); 20115 } 20116 , dispatch = function(filesaver, event_types, event) { 20117 event_types = [].concat(event_types); 20118 var i = event_types.length; 20119 while (i--) { 20120 var listener = filesaver["on" + event_types[i]]; 20121 if (typeof listener === "function") { 20122 try { 20123 listener.call(filesaver, event || filesaver); 20124 } catch (ex) { 20125 throw_outside(ex); 20126 } 20127 } 20128 } 20129 } 20130 , auto_bom = function(blob) { 20131 // prepend BOM for UTF-8 XML and text/* types (including HTML) 20132 // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 20133 if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 20134 return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); 20135 } 20136 return blob; 20137 } 20138 , FileSaver = function(blob, name, no_auto_bom) { 20139 if (!no_auto_bom) { 20140 blob = auto_bom(blob); 20141 } 20142 // First try a.download, then web filesystem, then object URLs 20143 var 20144 filesaver = this 20145 , type = blob.type 20146 , force = type === force_saveable_type 20147 , object_url 20148 , dispatch_all = function() { 20149 dispatch(filesaver, "writestart progress write writeend".split(" ")); 20150 } 20151 // on any filesys errors revert to saving with object URLs 20152 , fs_error = function() { 20153 if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { 20154 // Safari doesn't allow downloading of blob urls 20155 var reader = new FileReader(); 20156 reader.onloadend = function() { 20157 var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); 20158 var popup = view.open(url, '_blank'); 20159 if(!popup) view.location.href = url; 20160 url=undefined; // release reference before dispatching 20161 filesaver.readyState = filesaver.DONE; 20162 dispatch_all(); 20163 };
20164 reader.readAsDataURL(blob); 20165 filesaver.readyState = filesaver.INIT; 20166 return; 20167 } 20168 // don't create more object URLs than needed 20169 if (!object_url) { 20170 object_url = get_URL().createObjectURL(blob); 20171 } 20172 if (force) { 20173 view.location.href = object_url; 20174 } else { 20175 var opened = view.open(object_url, "_blank"); 20176 if (!opened) { 20177 // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html 20178 view.location.href = object_url; 20179 } 20180 } 20181 filesaver.readyState = filesaver.DONE; 20182 dispatch_all(); 20183 revoke(object_url); 20184 } 20185 ; 20186 filesaver.readyState = filesaver.INIT; 20187 20188 if (can_use_save_link) { 20189 object_url = get_URL().createObjectURL(blob); 20190 setTimeout(function() { 20191 save_link.href = object_url; 20192 save_link.download = name; 20193 click(save_link); 20194 dispatch_all(); 20195 revoke(object_url); 20196 filesaver.readyState = filesaver.DONE; 20197 }); 20198 return; 20199 } 20200 20201 fs_error(); 20202 } 20203 , FS_proto = FileSaver.prototype 20204 , saveAs = function(blob, name, no_auto_bom) { 20205 return new FileSaver(blob, name || blob.name || "download", no_auto_bom); 20206 } 20207 ; 20208 // IE 10+ (native saveAs) 20209 if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 20210 return function(blob, name, no_auto_bom) { 20211 name = name || blob.name || "download"; 20212 20213 if (!no_auto_bom) { 20214 blob = auto_bom(blob); 20215 } 20216 return navigator.msSaveOrOpenBlob(blob, name); 20217 }; 20218 } 20219 20220 FS_proto.abort = function(){}; 20221 FS_proto.readyState = FS_proto.INIT = 0; 20222 FS_proto.WRITING = 1; 20223 FS_proto.DONE = 2; 20224 20225 FS_proto.error = 20226 FS_proto.onwritestart = 20227 FS_proto.onprogress = 20228 FS_proto.onwrite = 20229 FS_proto.onabort = 20230 FS_proto.onerror = 20231 FS_proto.onwriteend = 20232 null; 20233 20234 return saveAs; 20235}( 20236 typeof self !== "undefined" && self 20237 || typeof window !== "undefined" && window 20238 || this.content 20239)); 20240 20241 20242// Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` 20243// since this file can be loaded before Button's core! 20244DataTable.fileSave = _saveAs; 20245 20246 20247/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20248 * Local (private) functions 20249 */ 20250 20251/** 20252 * Get the sheet name for Excel exports. 20253 * 20254 * @param {object} config Button configuration 20255 */ 20256var _sheetname = function ( config ) 20257{ 20258 var sheetName = 'Sheet1'; 20259 20260 if ( config.sheetName ) { 20261 sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, ''); 20262 } 20263 20264 return sheetName; 20265}; 20266 20267/** 20268 * Get the newline character(s) 20269 * 20270 * @param {object} config Button configuration 20271 * @return {string} Newline character 20272 */ 20273var _newLine = function ( config ) 20274{ 20275 return config.newline ? 20276 config.newline : 20277 navigator.userAgent.match(/Windows/) ? 20278 '\r\n' : 20279 '\n'; 20280}; 20281 20282/** 20283 * Combine the data from the `buttons.exportData` method into a string that 20284 * will be used in the export file. 20285 * 20286 * @param {DataTable.Api} dt DataTables API instance 20287 * @param {object} config Button configuration 20288 * @return {object} The data to export 20289 */ 20290var _exportData = function ( dt, config ) 20291{ 20292 var newLine = _newLine( config ); 20293 var data = dt.buttons.exportData( config.exportOptions ); 20294 var boundary = config.fieldBoundary; 20295 var separator = config.fieldSeparator; 20296 var reBoundary = new RegExp( boundary, 'g' ); 20297 var escapeChar = config.escapeChar !== undefined ? 20298 config.escapeChar : 20299 '\\'; 20300 var join = function ( a ) { 20301 var s = ''; 20302 20303 // If there is a field boundary, then we might need to escape it in 20304 // the source data 20305 for ( var i=0, ien=a.length ; i<ien ; i++ ) { 20306 if ( i > 0 ) { 20307 s += separator; 20308 } 20309 20310 s += boundary ? 20311 boundary + ('' + a[i]).replace( reBoundary, escapeChar+boundary ) + boundary : 20312 a[i]; 20313 } 20314 20315 return s; 20316 }; 20317 20318 var header = config.header ? join( data.header )+newLine : ''; 20319 var footer = config.footer && data.footer ? newLine+join( data.footer ) : ''; 20320 var body = []; 20321 20322 for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { 20323 body.push( join( data.body[i] ) ); 20324 } 20325 20326 return { 20327 str: header + body.join( newLine ) + footer, 20328 rows: body.length 20329 }; 20330}; 20331 20332/** 20333 * Older versions of Safari (prior to tech preview 18) don't support the 20334 * download option required. 20335 * 20336 * @return {Boolean} `true` if old Safari 20337 */ 20338var _isDuffSafari = function () 20339{ 20340 var safari = navigator.userAgent.indexOf('Safari') !== -1 && 20341 navigator.userAgent.indexOf('Chrome') === -1 && 20342 navigator.userAgent.indexOf('Opera') === -1; 20343 20344 if ( ! safari ) { 20345 return false; 20346 } 20347
20348 var version = navigator.userAgent.match( /AppleWebKit\/(\d+\.\d+)/ ); 20349 if ( version && version.length > 1 && version[1]*1 < 603.1 ) { 20350 return true; 20351 } 20352 20353 return false; 20354}; 20355 20356/** 20357 * Convert from numeric position to letter for column names in Excel 20358 * @param {int} n Column number 20359 * @return {string} Column letter(s) name 20360 */ 20361function createCellPos( n ){ 20362 var ordA = 'A'.charCodeAt(0); 20363 var ordZ = 'Z'.charCodeAt(0); 20364 var len = ordZ - ordA + 1; 20365 var s = ""; 20366 20367 while( n >= 0 ) { 20368 s = String.fromCharCode(n % len + ordA) + s; 20369 n = Math.floor(n / len) - 1; 20370 } 20371 20372 return s; 20373} 20374 20375try { 20376 var _serialiser = new XMLSerializer(); 20377 var _ieExcel; 20378} 20379catch (t) {} 20380 20381/** 20382 * Recursively add XML files from an object's structure to a ZIP file. This 20383 * allows the XSLX file to be easily defined with an object's structure matching 20384 * the files structure. 20385 * 20386 * @param {JSZip} zip ZIP package 20387 * @param {object} obj Object to add (recursive) 20388 */ 20389function _addToZip( zip, obj ) { 20390 if ( _ieExcel === undefined ) { 20391 // Detect if we are dealing with IE's _awful_ serialiser by seeing if it 20392 // drop attributes 20393 _ieExcel = _serialiser 20394 .serializeToString( 20395 ( new window.DOMParser() ).parseFromString( excelStrings['xl/worksheets/sheet1.xml'], 'text/xml' ) 20396 ) 20397 .indexOf( 'xmlns:r' ) === -1; 20398 } 20399 20400 $.each( obj, function ( name, val ) { 20401 if ( $.isPlainObject( val ) ) { 20402 var newDir = zip.folder( name ); 20403 _addToZip( newDir, val ); 20404 } 20405 else { 20406 if ( _ieExcel ) { 20407 // IE's XML serialiser will drop some name space attributes from 20408 // from the root node, so we need to save them. Do this by 20409 // replacing the namespace nodes with a regular attribute that 20410 // we convert back when serialised. Edge does not have this 20411 // issue 20412 var worksheet = val.childNodes[0]; 20413 var i, ien; 20414 var attrs = []; 20415 20416 for ( i=worksheet.attributes.length-1 ; i>=0 ; i-- ) { 20417 var attrName = worksheet.attributes[i].nodeName; 20418 var attrValue = worksheet.attributes[i].nodeValue; 20419 20420 if ( attrName.indexOf( ':' ) !== -1 ) { 20421 attrs.push( { name: attrName, value: attrValue } ); 20422 20423 worksheet.removeAttribute( attrName ); 20424 } 20425 } 20426 20427 for ( i=0, ien=attrs.length ; i<ien ; i++ ) { 20428 var attr = val.createAttribute( attrs[i].name.replace( ':', '_dt_b_namespace_token_' ) ); 20429 attr.value = attrs[i].value; 20430 worksheet.setAttributeNode( attr ); 20431 } 20432 } 20433 20434 var str = _serialiser.serializeToString(val); 20435 20436 // Fix IE's XML 20437 if ( _ieExcel ) { 20438 // IE doesn't include the XML declaration 20439 if ( str.indexOf( '<?xml' ) === -1 ) { 20440 str = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+str; 20441 } 20442 20443 // Return namespace attributes to being as such 20444 str = str.replace( /_dt_b_namespace_token_/g, ':' ); 20445 20446 // Remove testing name space that IE puts into the space preserve attr 20447 str = str.replace( /xmlns:NS[\d]+="" NS[\d]+:/g, '' ); 20448 } 20449 20450 // Safari, IE and Edge will put empty name space attributes onto 20451 // various elements making them useless. This strips them out 20452 str = str.replace( /<([^<>]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>' ); 20453 20454 zip.file( name, str ); 20455 } 20456 } ); 20457} 20458 20459/** 20460 * Create an XML node and add any children, attributes, etc without needing to 20461 * be verbose in the DOM. 20462 * 20463 * @param {object} doc XML document 20464 * @param {string} nodeName Node name 20465 * @param {object} opts Options - can be `attr` (attributes), `children` 20466 * (child nodes) and `text` (text content) 20467 * @return {node} Created node 20468 */ 20469function _createNode( doc, nodeName, opts ) { 20470 var tempNode = doc.createElement( nodeName ); 20471 20472 if ( opts ) { 20473 if ( opts.attr ) { 20474 $(tempNode).attr( opts.attr ); 20475 } 20476 20477 if ( opts.children ) { 20478 $.each( opts.children, function ( key, value ) { 20479 tempNode.appendChild( value ); 20480 } ); 20481 } 20482 20483 if ( opts.text !== null && opts.text !== undefined ) { 20484 tempNode.appendChild( doc.createTextNode( opts.text ) ); 20485 } 20486 } 20487 20488 return tempNode; 20489} 20490 20491/** 20492 * Get the width for an Excel column based on the contents of that column 20493 * @param {object} data Data for export 20494 * @param {int} col Column index 20495 * @return {int} Column width 20496 */ 20497function _excelColWidth( data, col ) { 20498 var max = data.header[col].length; 20499 var len, lineSplit, str; 20500 20501 if ( data.footer && data.footer[col].length > max ) { 20502 max = data.footer[col].length; 20503 } 20504 20505 for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { 20506 var point = data.body[i][col]; 20507 str = point !== null && point !== undefined ? 20508 point.toString() : 20509 ''; 20510 20511 // If there is a newline character, workout the width of the column 20512 // based on the longest line in the string 20513 if ( str.indexOf('\n') !== -1 ) { 20514 lineSplit = str.split('\n'); 20515 lineSplit.sort( function (a, b) { 20516 return b.length - a.length; 20517 } ); 20518 20519 len = lineSplit[0].length; 20520 } 20521 else { 20522 len = str.length; 20523 } 20524 20525 if ( len > max ) { 20526 max = len; 20527 } 20528 20529 // Max width rather than having potentially massive column widths 20530 if ( max > 40 ) { 20531 return 54; // 40 * 1.35 20532 } 20533 } 20534 20535 max *= 1.35; 20536 20537 // And a min width 20538 return max > 6 ? max : 6; 20539} 20540 20541// Excel - Pre-defined strings to build a basic XLSX file 20542var excelStrings = { 20543 "_rels/.rels": 20544 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ 20545 '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+ 20546 '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'+ 20547 '</Relationships>', 20548 20549 "xl/_rels/workbook.xml.rels": 20550 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ 20551 '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+ 20552 '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'+ 20553 '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'+ 20554 '</Relationships>', 20555 20556 "[Content_Types].xml": 20557 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ 20558 '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'+ 20559 '<Default Extension="xml" ContentType="application/xml" />'+ 20560 '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />'+ 20561 '<Default Extension="jpeg" ContentType="image/jpeg" />'+
20562 '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />'+ 20563 '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />'+ 20564 '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" />'+ 20565 '</Types>', 20566 20567 "xl/workbook.xml": 20568 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ 20569 '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+ 20570 '<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>'+ 20571 '<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>'+ 20572 '<bookViews>'+ 20573 '<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>'+ 20574 '</bookViews>'+ 20575 '<sheets>'+ 20576 '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>'+ 20577 '</sheets>'+ 20578 '<definedNames/>'+ 20579 '</workbook>', 20580 20581 "xl/worksheets/sheet1.xml": 20582 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ 20583 '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'+ 20584 '<sheetData/>'+ 20585 '<mergeCells count="0"/>'+ 20586 '</worksheet>', 20587 20588 "xl/styles.xml": 20589 '<?xml version="1.0" encoding="UTF-8"?>'+ 20590 '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'+ 20591 '<numFmts count="6">'+ 20592 '<numFmt numFmtId="164" formatCode="#,##0.00_-\ [$$-45C]"/>'+ 20593 '<numFmt numFmtId="165" formatCode=""£"#,##0.00"/>'+ 20594 '<numFmt numFmtId="166" formatCode="[$â¬-2]\ #,##0.00"/>'+ 20595 '<numFmt numFmtId="167" formatCode="0.0%"/>'+ 20596 '<numFmt numFmtId="168" formatCode="#,##0;(#,##0)"/>'+ 20597 '<numFmt numFmtId="169" formatCode="#,##0.00;(#,##0.00)"/>'+ 20598 '</numFmts>'+ 20599 '<fonts count="5" x14ac:knownFonts="1">'+ 20600 '<font>'+ 20601 '<sz val="11" />'+ 20602 '<name val="Calibri" />'+ 20603 '</font>'+ 20604 '<font>'+ 20605 '<sz val="11" />'+ 20606 '<name val="Calibri" />'+ 20607 '<color rgb="FFFFFFFF" />'+ 20608 '</font>'+ 20609 '<font>'+ 20610 '<sz val="11" />'+ 20611 '<name val="Calibri" />'+ 20612 '<b />'+ 20613 '</font>'+ 20614 '<font>'+ 20615 '<sz val="11" />'+ 20616 '<name val="Calibri" />'+ 20617 '<i />'+ 20618 '</font>'+ 20619 '<font>'+ 20620 '<sz val="11" />'+ 20621 '<name val="Calibri" />'+ 20622 '<u />'+ 20623 '</font>'+ 20624 '</fonts>'+ 20625 '<fills count="6">'+ 20626 '<fill>'+ 20627 '<patternFill patternType="none" />'+ 20628 '</fill>'+ 20629 '<fill>'+ // Excel appears to use this as a dotted background regardless of values but 20630 '<patternFill patternType="none" />'+ // to be valid to the schema, use a patternFill 20631 '</fill>'+ 20632 '<fill>'+ 20633 '<patternFill patternType="solid">'+ 20634 '<fgColor rgb="FFD9D9D9" />'+ 20635 '<bgColor indexed="64" />'+ 20636 '</patternFill>'+ 20637 '</fill>'+ 20638 '<fill>'+ 20639 '<patternFill patternType="solid">'+ 20640 '<fgColor rgb="FFD99795" />'+ 20641 '<bgColor indexed="64" />'+ 20642 '</patternFill>'+ 20643 '</fill>'+ 20644 '<fill>'+ 20645 '<patternFill patternType="solid">'+ 20646 '<fgColor rgb="ffc6efce" />'+ 20647 '<bgColor indexed="64" />'+ 20648 '</patternFill>'+ 20649 '</fill>'+ 20650 '<fill>'+ 20651 '<patternFill patternType="solid">'+ 20652 '<fgColor rgb="ffc6cfef" />'+ 20653 '<bgColor indexed="64" />'+ 20654 '</patternFill>'+ 20655 '</fill>'+ 20656 '</fills>'+ 20657 '<borders count="2">'+ 20658 '<border>'+ 20659 '<left />'+ 20660 '<right />'+ 20661 '<top />'+ 20662 '<bottom />'+ 20663 '<diagonal />'+ 20664 '</border>'+ 20665 '<border diagonalUp="false" diagonalDown="false">'+ 20666 '<left style="thin">'+ 20667 '<color auto="1" />'+ 20668 '</left>'+ 20669 '<right style="thin">'+ 20670 '<color auto="1" />'+ 20671 '</right>'+ 20672 '<top style="thin">'+ 20673 '<color auto="1" />'+ 20674 '</top>'+ 20675 '<bottom style="thin">'+ 20676 '<color auto="1" />'+ 20677 '</bottom>'+ 20678 '<diagonal />'+ 20679 '</border>'+ 20680 '</borders>'+ 20681 '<cellStyleXfs count="1">'+ 20682 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />'+ 20683 '</cellStyleXfs>'+ 20684 '<cellXfs count="68">'+ 20685 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20686 '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20687 '<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20688 '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20689 '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20690 '<xf numFmtId="0" fontId="0" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20691 '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20692 '<xf numFmtId="0" fontId="2" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20693 '<xf numFmtId="0" fontId="3" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20694 '<xf numFmtId="0" fontId="4" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20695 '<xf numFmtId="0" fontId="0" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20696 '<xf numFmtId="0" fontId="1" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20697 '<xf numFmtId="0" fontId="2" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20698 '<xf numFmtId="0" fontId="3" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20699 '<xf numFmtId="0" fontId="4" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20700 '<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20701 '<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20702 '<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20703 '<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20704 '<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20705 '<xf numFmtId="0" fontId="0" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20706 '<xf numFmtId="0" fontId="1" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20707 '<xf numFmtId="0" fontId="2" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20708 '<xf numFmtId="0" fontId="3" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20709 '<xf numFmtId="0" fontId="4" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20710 '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20711 '<xf numFmtId="0" fontId="1" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20712 '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20713 '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20714 '<xf numFmtId="0" fontId="4" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20715 '<xf numFmtId="0" fontId="0" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20716 '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20717 '<xf numFmtId="0" fontId="2" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20718 '<xf numFmtId="0" fontId="3" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20719 '<xf numFmtId="0" fontId="4" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20720 '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20721 '<xf numFmtId="0" fontId="1" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20722 '<xf numFmtId="0" fontId="2" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20723 '<xf numFmtId="0" fontId="3" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20724 '<xf numFmtId="0" fontId="4" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20725 '<xf numFmtId="0" fontId="0" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20726 '<xf numFmtId="0" fontId="1" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20727 '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20728 '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20729 '<xf numFmtId="0" fontId="4" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20730 '<xf numFmtId="0" fontId="0" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20731 '<xf numFmtId="0" fontId="1" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20732 '<xf numFmtId="0" fontId="2" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20733 '<xf numFmtId="0" fontId="3" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20734 '<xf numFmtId="0" fontId="4" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ 20735 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20736 '<alignment horizontal="left"/>'+ 20737 '</xf>'+ 20738 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20739 '<alignment horizontal="center"/>'+ 20740 '</xf>'+ 20741 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20742 '<alignment horizontal="right"/>'+ 20743 '</xf>'+ 20744 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20745 '<alignment horizontal="fill"/>'+ 20746 '</xf>'+ 20747 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20748 '<alignment textRotation="90"/>'+ 20749 '</xf>'+ 20750 '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ 20751 '<alignment wrapText="1"/>'+ 20752 '</xf>'+ 20753 '<xf numFmtId="9" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20754 '<xf numFmtId="164" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20755 '<xf numFmtId="165" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20756 '<xf numFmtId="166" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20757 '<xf numFmtId="167" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20758 '<xf numFmtId="168" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20759 '<xf numFmtId="169" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20760 '<xf numFmtId="3" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20761 '<xf numFmtId="4" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20762 '<xf numFmtId="1" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20763 '<xf numFmtId="2" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20764 '<xf numFmtId="14" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+ 20765 '</cellXfs>'+ 20766 '<cellStyles count="1">'+ 20767 '<cellStyle name="Normal" xfId="0" builtinId="0" />'+ 20768 '</cellStyles>'+ 20769 '<dxfs count="0" />'+ 20770 '<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4" />'+ 20771 '</styleSheet>' 20772}; 20773// Note we could use 3 `for` loops for the styles, but when gzipped there is
20774// virtually no difference in size, since the above can be easily compressed 20775 20776// Pattern matching for special number formats. Perhaps this should be exposed 20777// via an API in future? 20778// Ref: section 3.8.30 - built in formatters in open spreadsheet 20779// https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf 20780var _excelSpecials = [ 20781 { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d/100; } }, // Percent with d.p. 20782 { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d/100; } }, // Percent 20783 { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars 20784 { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds 20785 { match: /^\-?â¬[\d,]+.?\d*$/, style: 59 }, // Euros 20786 { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators 20787 { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators 20788 { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets 20789 { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p. 20790 { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators 20791 { match: /^\-?[\d,]+\.\d{2}$/, style: 64 }, 20792 { match: /^[\d]{4}\-[01][\d]\-[0123][\d]$/, style: 67, fmt: function (d) {return Math.round(25569 + (Date.parse(d) / (86400 * 1000)));}} //Date yyyy-mm-dd 20793]; 20794 20795 20796 20797/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20798 * Buttons 20799 */ 20800 20801// 20802// Copy to clipboard 20803// 20804DataTable.ext.buttons.copyHtml5 = { 20805 className: 'buttons-copy buttons-html5', 20806 20807 text: function ( dt ) { 20808 return dt.i18n( 'buttons.copy', 'Copy' ); 20809 }, 20810 20811 action: function ( e, dt, button, config ) { 20812 this.processing( true ); 20813 20814 var that = this; 20815 var exportData = _exportData( dt, config ); 20816 var info = dt.buttons.exportInfo( config ); 20817 var newline = _newLine(config); 20818 var output = exportData.str; 20819 var hiddenDiv = $('<div/>') 20820 .css( { 20821 height: 1, 20822 width: 1, 20823 overflow: 'hidden', 20824 position: 'fixed', 20825 top: 0, 20826 left: 0 20827 } ); 20828 20829 if ( info.title ) { 20830 output = info.title + newline + newline + output; 20831 } 20832 20833 if ( info.messageTop ) { 20834 output = info.messageTop + newline + newline + output; 20835 } 20836 20837 if ( info.messageBottom ) { 20838 output = output + newline + newline + info.messageBottom; 20839 } 20840 20841 if ( config.customize ) { 20842 output = config.customize( output, config, dt ); 20843 } 20844 20845 var textarea = $('<textarea readonly/>') 20846 .val( output ) 20847 .appendTo( hiddenDiv ); 20848 20849 // For browsers that support the copy execCommand, try to use it 20850 if ( document.queryCommandSupported('copy') ) { 20851 hiddenDiv.appendTo( dt.table().container() ); 20852 textarea[0].focus(); 20853 textarea[0].select(); 20854 20855 try { 20856 var successful = document.execCommand( 'copy' ); 20857 hiddenDiv.remove(); 20858 20859 if (successful) { 20860 dt.buttons.info( 20861 dt.i18n( 'buttons.copyTitle', 'Copy to clipboard' ), 20862 dt.i18n( 'buttons.copySuccess', { 20863 1: 'Copied one row to clipboard', 20864 _: 'Copied %d rows to clipboard' 20865 }, exportData.rows ), 20866 2000 20867 ); 20868 20869 this.processing( false ); 20870 return; 20871 } 20872 } 20873 catch (t) {} 20874 } 20875 20876 // Otherwise we show the text box and instruct the user to use it 20877 var message = $('<span>'+dt.i18n( 'buttons.copyKeys', 20878 'Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>'+ 20879 'To cancel, click this message or press escape.' )+'</span>' 20880 ) 20881 .append( hiddenDiv ); 20882 20883 dt.buttons.info( dt.i18n( 'buttons.copyTitle', 'Copy to clipboard' ), message, 0 ); 20884 20885 // Select the text so when the user activates their system clipboard 20886 // it will copy that text 20887 textarea[0].focus(); 20888 textarea[0].select(); 20889 20890 // Event to hide the message when the user is done 20891 var container = $(message).closest('.dt-button-info'); 20892 var close = function () { 20893 container.off( 'click.buttons-copy' ); 20894 $(document).off( '.buttons-copy' ); 20895 dt.buttons.info( false ); 20896 }; 20897 20898 container.on( 'click.buttons-copy', close ); 20899 $(document) 20900 .on( 'keydown.buttons-copy', function (e) { 20901 if ( e.keyCode === 27 ) { // esc 20902 close(); 20903 that.processing( false ); 20904 } 20905 } ) 20906 .on( 'copy.buttons-copy cut.buttons-copy', function () { 20907 close(); 20908 that.processing( false ); 20909 } ); 20910 }, 20911 20912 exportOptions: {}, 20913 20914 fieldSeparator: '\t', 20915 20916 fieldBoundary: '', 20917 20918 header: true, 20919 20920 footer: false, 20921 20922 title: '*', 20923 20924 messageTop: '*', 20925 20926 messageBottom: '*' 20927}; 20928 20929// 20930// CSV export 20931// 20932DataTable.ext.buttons.csvHtml5 = { 20933 bom: false, 20934 20935 className: 'buttons-csv buttons-html5', 20936 20937 available: function () { 20938 return window.FileReader !== undefined && window.Blob; 20939 }, 20940 20941 text: function ( dt ) { 20942 return dt.i18n( 'buttons.csv', 'CSV' ); 20943 }, 20944 20945 action: function ( e, dt, button, config ) { 20946 this.processing( true ); 20947 20948 // Set the text 20949 var output = _exportData( dt, config ).str; 20950 var info = dt.buttons.exportInfo(config); 20951 var charset = config.charset; 20952 20953 if ( config.customize ) { 20954 output = config.customize( output, config, dt ); 20955 } 20956 20957 if ( charset !== false ) { 20958 if ( ! charset ) { 20959 charset = document.characterSet || document.charset; 20960 } 20961 20962 if ( charset ) { 20963 charset = ';charset='+charset; 20964 } 20965 } 20966 else { 20967 charset = ''; 20968 } 20969 20970 if ( config.bom ) { 20971 output = String.fromCharCode(0xFEFF) + output; 20972 } 20973 20974 _saveAs( 20975 new Blob( [output], {type: 'text/csv'+charset} ), 20976 info.filename, 20977 true 20978 ); 20979 20980 this.processing( false ); 20981 }, 20982 20983 filename: '*', 20984 20985 extension: '.csv', 20986 20987 exportOptions: {}, 20988 20989 fieldSeparator: ',', 20990 20991 fieldBoundary: '"', 20992 20993 escapeChar: '"', 20994
20995 charset: null, 20996 20997 header: true, 20998 20999 footer: false 21000}; 21001 21002// 21003// Excel (xlsx) export 21004// 21005DataTable.ext.buttons.excelHtml5 = { 21006 className: 'buttons-excel buttons-html5', 21007 21008 available: function () { 21009 return window.FileReader !== undefined && _jsZip() !== undefined && ! _isDuffSafari() && _serialiser; 21010 }, 21011 21012 text: function ( dt ) { 21013 return dt.i18n( 'buttons.excel', 'Excel' ); 21014 }, 21015 21016 action: function ( e, dt, button, config ) { 21017 this.processing( true ); 21018 21019 var that = this; 21020 var rowPos = 0; 21021 var dataStartRow, dataEndRow; 21022 var getXml = function ( type ) { 21023 var str = excelStrings[ type ]; 21024 21025 //str = str.replace( /xmlns:/g, 'xmlns_' ).replace( /mc:/g, 'mc_' ); 21026 21027 return $.parseXML( str ); 21028 }; 21029 var rels = getXml('xl/worksheets/sheet1.xml'); 21030 var relsGet = rels.getElementsByTagName( "sheetData" )[0]; 21031 21032 var xlsx = { 21033 _rels: { 21034 ".rels": getXml('_rels/.rels') 21035 }, 21036 xl: { 21037 _rels: { 21038 "workbook.xml.rels": getXml('xl/_rels/workbook.xml.rels') 21039 }, 21040 "workbook.xml": getXml('xl/workbook.xml'), 21041 "styles.xml": getXml('xl/styles.xml'), 21042 "worksheets": { 21043 "sheet1.xml": rels 21044 } 21045 21046 }, 21047 "[Content_Types].xml": getXml('[Content_Types].xml') 21048 }; 21049 21050 var data = dt.buttons.exportData( config.exportOptions ); 21051 var currentRow, rowNode; 21052 var addRow = function ( row ) { 21053 currentRow = rowPos+1; 21054 rowNode = _createNode( rels, "row", { attr: {r:currentRow} } ); 21055 21056 for ( var i=0, ien=row.length ; i<ien ; i++ ) { 21057 // Concat both the Cell Columns as a letter and the Row of the cell. 21058 var cellId = createCellPos(i) + '' + currentRow; 21059 var cell = null; 21060 21061 // For null, undefined of blank cell, continue so it doesn't create the _createNode 21062 if ( row[i] === null || row[i] === undefined || row[i] === '' ) { 21063 if ( config.createEmptyCells === true ) { 21064 row[i] = ''; 21065 } 21066 else { 21067 continue; 21068 } 21069 } 21070 21071 var originalContent = row[i]; 21072 row[i] = typeof row[i].trim === 'function' 21073 ? row[i].trim() 21074 : row[i]; 21075 21076 // Special number formatting options 21077 for ( var j=0, jen=_excelSpecials.length ; j<jen ; j++ ) { 21078 var special = _excelSpecials[j]; 21079 21080 // TODO Need to provide the ability for the specials to say 21081 // if they are returning a string, since at the moment it is 21082 // assumed to be a number 21083 if ( row[i].match && ! row[i].match(/^0\d+/) && row[i].match( special.match ) ) { 21084 var val = row[i].replace(/[^\d\.\-]/g, ''); 21085 21086 if ( special.fmt ) { 21087 val = special.fmt( val ); 21088 } 21089 21090 cell = _createNode( rels, 'c', { 21091 attr: { 21092 r: cellId, 21093 s: special.style 21094 }, 21095 children: [ 21096 _createNode( rels, 'v', { text: val } ) 21097 ] 21098 } ); 21099 21100 break; 21101 } 21102 } 21103 21104 if ( ! cell ) { 21105 if ( typeof row[i] === 'number' || ( 21106 row[i].match && 21107 row[i].match(/^-?\d+(\.\d+)?([eE]\-?\d+)?$/) && // Includes exponential format 21108 ! row[i].match(/^0\d+/) ) 21109 ) { 21110 // Detect numbers - don't match numbers with leading zeros 21111 // or a negative anywhere but the start 21112 cell = _createNode( rels, 'c', { 21113 attr: { 21114 t: 'n', 21115 r: cellId 21116 }, 21117 children: [ 21118 _createNode( rels, 'v', { text: row[i] } ) 21119 ] 21120 } ); 21121 } 21122 else { 21123 // String output - replace non standard characters for text output 21124 var text = ! originalContent.replace ? 21125 originalContent : 21126 originalContent.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, ''); 21127 21128 cell = _createNode( rels, 'c', { 21129 attr: { 21130 t: 'inlineStr', 21131 r: cellId 21132 }, 21133 children:{ 21134 row: _createNode( rels, 'is', { 21135 children: { 21136 row: _createNode( rels, 't', { 21137 text: text, 21138 attr: { 21139 'xml:space': 'preserve' 21140 } 21141 } ) 21142 } 21143 } ) 21144 } 21145 } ); 21146 } 21147 } 21148 21149 rowNode.appendChild( cell ); 21150 } 21151 21152 relsGet.appendChild(rowNode); 21153 rowPos++; 21154 }; 21155 21156 if ( config.customizeData ) { 21157 config.customizeData( data ); 21158 } 21159 21160 var mergeCells = function ( row, colspan ) { 21161 var mergeCells = $('mergeCells', rels); 21162 21163 mergeCells[0].appendChild( _createNode( rels, 'mergeCell', { 21164 attr: { 21165 ref: 'A'+row+':'+createCellPos(colspan)+row 21166 } 21167 } ) ); 21168 mergeCells.attr( 'count', parseFloat(mergeCells.attr( 'count' ))+1 ); 21169 $('row:eq('+(row-1)+') c', rels).attr( 's', '51' ); // centre 21170 }; 21171 21172 // Title and top messages 21173 var exportInfo = dt.buttons.exportInfo( config ); 21174 if ( exportInfo.title ) { 21175 addRow( [exportInfo.title], rowPos ); 21176 mergeCells( rowPos, data.header.length-1 ); 21177 } 21178 21179 if ( exportInfo.messageTop ) { 21180 addRow( [exportInfo.messageTop], rowPos ); 21181 mergeCells( rowPos, data.header.length-1 ); 21182 } 21183 21184 21185 // Table itself 21186 if ( config.header ) { 21187 addRow( data.header, rowPos ); 21188 $('row:last c', rels).attr( 's', '2' ); // bold 21189 } 21190 21191 dataStartRow = rowPos; 21192 21193 for ( var n=0, ie=data.body.length ; n<ie ; n++ ) { 21194 addRow( data.body[n], rowPos ); 21195 } 21196 21197 dataEndRow = rowPos; 21198 21199 if ( config.footer && data.footer ) { 21200 addRow( data.footer, rowPos); 21201 $('row:last c', rels).attr( 's', '2' ); // bold 21202 } 21203 21204 // Below the table 21205 if ( exportInfo.messageBottom ) { 21206 addRow( [exportInfo.messageBottom], rowPos ); 21207 mergeCells( rowPos, data.header.length-1 ); 21208 } 21209 21210 // Set column widths 21211 var cols = _createNode( rels, 'cols' ); 21212 $('worksheet', rels).prepend( cols ); 21213 21214 for ( var i=0, ien=data.header.length ; i<ien ; i++ ) { 21215 cols.appendChild( _createNode( rels, 'col', { 21216 attr: { 21217 min: i+1, 21218 max: i+1, 21219 width: _excelColWidth( data, i ), 21220 customWidth: 1 21221 } 21222 } ) ); 21223 } 21224 21225 // Workbook modifications 21226 var workbook = xlsx.xl['workbook.xml']; 21227 21228 $( 'sheets sheet', workbook ).attr( 'name', _sheetname( config ) ); 21229 21230 // Auto filter for columns 21231 if ( config.autoFilter ) { 21232 $('mergeCells', rels).before( _createNode( rels, 'autoFilter', { 21233 attr: { 21234 ref: 'A'+dataStartRow+':'+createCellPos(data.header.length-1)+dataEndRow 21235 } 21236 } ) ); 21237 21238 $('definedNames', workbook).append( _createNode( workbook, 'definedName', { 21239 attr: { 21240 name: '_xlnm._FilterDatabase', 21241 localSheetId: '0', 21242 hidden: 1 21243 }, 21244 text: _sheetname(config)+'!$A$'+dataStartRow+':'+createCellPos(data.header.length-1)+dataEndRow 21245 } ) ); 21246 } 21247 21248 // Let the developer customise the document if they want to 21249 if ( config.customize ) { 21250 config.customize( xlsx, config, dt ); 21251 } 21252 21253 // Excel doesn't like an empty mergeCells tag 21254 if ( $('mergeCells', rels).children().length === 0 ) { 21255 $('mergeCells', rels).remove(); 21256 } 21257 21258 var jszip = _jsZip(); 21259 var zip = new jszip(); 21260 var zipConfig = { 21261 compression: "DEFLATE", 21262 type: 'blob', 21263 mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 21264 }; 21265 21266 _addToZip( zip, xlsx ); 21267 21268 // Modern Excel has a 218 character limit on the file name + path of the file (why!?)
21269 // https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 21270 // So we truncate to allow for this. 21271 var filename = exportInfo.filename; 21272 21273 if (filename > 175) { 21274 filename = filename.substr(0, 175); 21275 } 21276 21277 if ( zip.generateAsync ) { 21278 // JSZip 3+ 21279 zip 21280 .generateAsync( zipConfig ) 21281 .then( function ( blob ) { 21282 _saveAs( blob, filename ); 21283 that.processing( false ); 21284 } ); 21285 } 21286 else { 21287 // JSZip 2.5 21288 _saveAs( 21289 zip.generate( zipConfig ), 21290 filename 21291 ); 21292 this.processing( false ); 21293 } 21294 }, 21295 21296 filename: '*', 21297 21298 extension: '.xlsx', 21299 21300 exportOptions: {}, 21301 21302 header: true, 21303 21304 footer: false, 21305 21306 title: '*', 21307 21308 messageTop: '*', 21309 21310 messageBottom: '*', 21311 21312 createEmptyCells: false, 21313 21314 autoFilter: false, 21315 21316 sheetName: '' 21317}; 21318 21319// 21320// PDF export - using pdfMake - http://pdfmake.org 21321// 21322DataTable.ext.buttons.pdfHtml5 = { 21323 className: 'buttons-pdf buttons-html5', 21324 21325 available: function () { 21326 return window.FileReader !== undefined && _pdfMake(); 21327 }, 21328 21329 text: function ( dt ) { 21330 return dt.i18n( 'buttons.pdf', 'PDF' ); 21331 }, 21332 21333 action: function ( e, dt, button, config ) { 21334 this.processing( true ); 21335 21336 var that = this; 21337 var data = dt.buttons.exportData( config.exportOptions ); 21338 var info = dt.buttons.exportInfo( config ); 21339 var rows = []; 21340 21341 if ( config.header ) { 21342 rows.push( $.map( data.header, function ( d ) { 21343 return { 21344 text: typeof d === 'string' ? d : d+'', 21345 style: 'tableHeader' 21346 }; 21347 } ) ); 21348 } 21349 21350 for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { 21351 rows.push( $.map( data.body[i], function ( d ) { 21352 if ( d === null || d === undefined ) { 21353 d = ''; 21354 } 21355 return { 21356 text: typeof d === 'string' ? d : d+'', 21357 style: i % 2 ? 'tableBodyEven' : 'tableBodyOdd' 21358 }; 21359 } ) ); 21360 } 21361 21362 if ( config.footer && data.footer) { 21363 rows.push( $.map( data.footer, function ( d ) { 21364 return { 21365 text: typeof d === 'string' ? d : d+'', 21366 style: 'tableFooter' 21367 }; 21368 } ) ); 21369 } 21370 21371 var doc = { 21372 pageSize: config.pageSize, 21373 pageOrientation: config.orientation, 21374 content: [ 21375 { 21376 table: { 21377 headerRows: 1, 21378 body: rows 21379 }, 21380 layout: 'noBorders' 21381 } 21382 ], 21383 styles: { 21384 tableHeader: { 21385 bold: true, 21386 fontSize: 11, 21387 color: 'white', 21388 fillColor: '#2d4154', 21389 alignment: 'center' 21390 }, 21391 tableBodyEven: {}, 21392 tableBodyOdd: { 21393 fillColor: '#f3f3f3' 21394 }, 21395 tableFooter: { 21396 bold: true, 21397 fontSize: 11, 21398 color: 'white', 21399 fillColor: '#2d4154' 21400 }, 21401 title: {
21402 alignment: 'center', 21403 fontSize: 15 21404 }, 21405 message: {} 21406 }, 21407 defaultStyle: { 21408 fontSize: 10 21409 } 21410 }; 21411 21412 if ( info.messageTop ) { 21413 doc.content.unshift( { 21414 text: info.messageTop, 21415 style: 'message', 21416 margin: [ 0, 0, 0, 12 ] 21417 } ); 21418 } 21419 21420 if ( info.messageBottom ) { 21421 doc.content.push( { 21422 text: info.messageBottom, 21423 style: 'message', 21424 margin: [ 0, 0, 0, 12 ] 21425 } ); 21426 } 21427 21428 if ( info.title ) { 21429 doc.content.unshift( { 21430 text: info.title, 21431 style: 'title', 21432 margin: [ 0, 0, 0, 12 ] 21433 } ); 21434 } 21435 21436 if ( config.customize ) { 21437 config.customize( doc, config, dt ); 21438 } 21439 21440 var pdf = _pdfMake().createPdf( doc ); 21441 21442 if ( config.download === 'open' && ! _isDuffSafari() ) { 21443 pdf.open(); 21444 } 21445 else { 21446 pdf.download( info.filename ); 21447 } 21448 21449 this.processing( false ); 21450 }, 21451 21452 title: '*', 21453 21454 filename: '*', 21455 21456 extension: '.pdf', 21457 21458 exportOptions: {}, 21459 21460 orientation: 'portrait', 21461 21462 pageSize: 'A4', 21463 21464 header: true, 21465 21466 footer: false, 21467 21468 messageTop: '*', 21469 21470 messageBottom: '*', 21471 21472 customize: null, 21473 21474 download: 'download' 21475}; 21476 21477 21478return DataTable; 21479})); 21480 21481 21482/*! 21483 * Print button for Buttons and DataTables. 21484 * 2016 SpryMedia Ltd - datatables.net/license 21485 */ 21486 21487(function( factory ){ 21488 if ( typeof define === 'function' && define.amd ) { 21489 // AMD 21490 define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) { 21491 return factory( $, window, document ); 21492 } ); 21493 } 21494 else if ( typeof exports === 'object' ) { 21495 // CommonJS 21496 var jq = require('jquery'); 21497 var cjsRequires = function (root, $) { 21498 if ( ! $.fn.dataTable ) { 21499 require('datatables.net')(root, $); 21500 } 21501 21502 if ( ! $.fn.dataTable.Buttons ) { 21503 require('datatables.net-buttons')(root, $); 21504 } 21505 }; 21506 21507 if (typeof window !== 'undefined') { 21508 module.exports = function (root, $) { 21509 if ( ! root ) { 21510 // CommonJS environments without a window global must pass a 21511 // root. This will give an error otherwise 21512 root = window; 21513 } 21514 21515 if ( ! $ ) { 21516 $ = jq( root ); 21517 } 21518 21519 cjsRequires( root, $ ); 21520 return factory( $, root, root.document ); 21521 }; 21522 } 21523 else { 21524 cjsRequires( window, jq ); 21525 module.exports = factory( jq, window, window.document ); 21526 } 21527 } 21528 else { 21529 // Browser 21530 factory( jQuery, window, document ); 21531 } 21532}(function( $, window, document, undefined ) { 21533'use strict'; 21534var DataTable = $.fn.dataTable; 21535 21536 21537 21538var _link = document.createElement( 'a' ); 21539 21540/** 21541 * Clone link and style tags, taking into account the need to change the source 21542 * path. 21543 * 21544 * @param {node} el Element to convert 21545 */ 21546var _styleToAbs = function( el ) { 21547 var url; 21548 var clone = $(el).clone()[0]; 21549 var linkHost; 21550 21551 if ( clone.nodeName.toLowerCase() === 'link' ) { 21552 clone.href = _relToAbs( clone.href ); 21553 } 21554 21555 return clone.outerHTML; 21556}; 21557 21558/** 21559 * Convert a URL from a relative to an absolute address so it will work 21560 * correctly in the popup window which has no base URL. 21561 * 21562 * @param {string} href URL 21563 */ 21564var _relToAbs = function( href ) { 21565 // Assign to a link on the original page so the browser will do all the 21566 // hard work of figuring out where the file actually is 21567 _link.href = href; 21568 var linkHost = _link.host; 21569 21570 // IE doesn't have a trailing slash on the host 21571 // Chrome has it on the pathname 21572 if ( linkHost.indexOf('/') === -1 && _link.pathname.indexOf('/') !== 0) { 21573 linkHost += '/'; 21574 } 21575 21576 return _link.protocol+"//"+linkHost+_link.pathname+_link.search; 21577}; 21578 21579 21580DataTable.ext.buttons.print = { 21581 className: 'buttons-print', 21582 21583 text: function ( dt ) { 21584 return dt.i18n( 'buttons.print', 'Print' ); 21585 }, 21586 21587 action: function ( e, dt, button, config ) { 21588 var data = dt.buttons.exportData( 21589 $.extend( {decodeEntities: false}, config.exportOptions ) // XSS protection 21590 ); 21591 var exportInfo = dt.buttons.exportInfo( config ); 21592 var columnClasses = dt 21593 .columns( config.exportOptions.columns ) 21594 .flatten() 21595 .map( function (idx) { 21596 return dt.settings()[0].aoColumns[dt.column(idx).index()].sClass; 21597 } ) 21598 .toArray(); 21599 21600 var addRow = function ( d, tag ) { 21601 var str = '<tr>'; 21602 21603 for ( var i=0, ien=d.length ; i<ien ; i++ ) { 21604 // null and undefined aren't useful in the print output 21605 var dataOut = d[i] === null || d[i] === undefined ? 21606 '' : 21607 d[i]; 21608 var classAttr = columnClasses[i] ? 21609 'class="'+columnClasses[i]+'"' : 21610 ''; 21611 21612 str += '<'+tag+' '+classAttr+'>'+dataOut+'</'+tag+'>'; 21613 } 21614 21615 return str + '</tr>'; 21616 }; 21617 21618 // Construct a table for printing 21619 var html = '<table class="'+dt.table().node().className+'">'; 21620 21621 if ( config.header ) { 21622 html += '<thead>'+ addRow( data.header, 'th' ) +'</thead>'; 21623 } 21624 21625 html += '<tbody>'; 21626 for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { 21627 html += addRow( data.body[i], 'td' ); 21628 } 21629 html += '</tbody>'; 21630 21631 if ( config.footer && data.footer ) { 21632 html += '<tfoot>'+ addRow( data.footer, 'th' ) +'</tfoot>'; 21633 } 21634 html += '</table>'; 21635 21636 // Open a new window for the printable table 21637 var win = window.open( '', '' ); 21638 21639 if (! win) { 21640 dt.buttons.info( 21641 dt.i18n( 'buttons.printErrorTitle', 'Unable to open print view' ), 21642 dt.i18n( 'buttons.printErrorMsg', 'Please allow popups in your browser for this site to be able to view the print view.' ), 21643 5000 21644 ); 21645 21646 return; 21647 } 21648 21649 win.document.close(); 21650 21651 // Inject the title and also a copy of the style and link tags from this 21652 // document so the table can retain its base styling. Note that we have 21653 // to use string manipulation as IE won't allow elements to be created 21654 // in the host document and then appended to the new window. 21655 var head = '<title>'+exportInfo.title+'</title>'; 21656 $('style, link').each( function () { 21657 head += _styleToAbs( this ); 21658 } ); 21659 21660 try { 21661 win.document.head.innerHTML = head; // Work around for Edge 21662 } 21663 catch (e) { 21664 $(win.document.head).html( head ); // Old IE 21665 } 21666 21667 // Inject the table and other surrounding information 21668 win.document.body.innerHTML = 21669 '<h1>'+exportInfo.title+'</h1>'+ 21670 '<div>'+(exportInfo.messageTop || '')+'</div>'+ 21671 html+ 21672 '<div>'+(exportInfo.messageBottom || '')+'</div>'; 21673 21674 $(win.document.body).addClass('dt-print-view'); 21675 21676 $('img', win.document.body).each( function ( i, img ) { 21677 img.setAttribute( 'src', _relToAbs( img.getAttribute('src') ) ); 21678 } ); 21679 21680 if ( config.customize ) { 21681 config.customize( win, config, dt ); 21682 } 21683 21684 // Allow stylesheets time to load 21685 var autoPrint = function () { 21686 if ( config.autoPrint ) { 21687 win.print(); // blocking - so close will not 21688 win.close(); // execute until this is done 21689 } 21690 }; 21691 21692 if ( navigator.userAgent.match(/Trident\/\d.\d/) ) { // IE needs to call this without a setTimeout 21693 autoPrint(); 21694 } 21695 else { 21696 win.setTimeout( autoPrint, 1000 ); 21697 } 21698 }, 21699 21700 title: '*', 21701 21702 messageTop: '*', 21703 21704 messageBottom: '*', 21705 21706 exportOptions: {}, 21707 21708 header: true, 21709 21710 footer: false, 21711 21712 autoPrint: true, 21713 21714 customize: null 21715}; 21716 21717 21718return DataTable; 21719})); 21720 21721 21722/*! ColReorder 1.6.2 21723 * © SpryMedia Ltd - datatables.net/license 21724 */ 21725 21726(function( factory ){ 21727 if ( typeof define === 'function' && define.amd ) { 21728 // AMD 21729 define( ['jquery', 'datatables.net'], function ( $ ) { 21730 return factory( $, window, document ); 21731 } ); 21732 } 21733 else if ( typeof exports === 'object' ) { 21734 // CommonJS 21735 var jq = require('jquery'); 21736 var cjsRequires = function (root, $) { 21737 if ( ! $.fn.dataTable ) { 21738 require('datatables.net')(root, $); 21739 } 21740 }; 21741 21742 if (typeof window !== 'undefined') { 21743 module.exports = function (root, $) { 21744 if ( ! root ) { 21745 // CommonJS environments without a window global must pass a 21746 // root. This will give an error otherwise 21747 root = window; 21748 } 21749 21750 if ( ! $ ) { 21751 $ = jq( root ); 21752 } 21753 21754 cjsRequires( root, $ ); 21755 return factory( $, root, root.document ); 21756 }; 21757 } 21758 else { 21759 cjsRequires( window, jq ); 21760 module.exports = factory( jq, window, window.document ); 21761 } 21762 } 21763 else { 21764 // Browser 21765 factory( jQuery, window, document ); 21766 } 21767}(function( $, window, document, undefined ) { 21768'use strict'; 21769var DataTable = $.fn.dataTable; 21770 21771 21772 21773/** 21774 * @summary ColReorder
21775 * @description Provide the ability to reorder columns in a DataTable 21776 * @version 1.6.2 21777 * @author SpryMedia Ltd 21778 * @contact datatables.net 21779 * @copyright SpryMedia Ltd. 21780 * 21781 * This source file is free software, available under the following license: 21782 * MIT license - http://datatables.net/license/mit 21783 * 21784 * This source file is distributed in the hope that it will be useful, but 21785 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 21786 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 21787 * 21788 * For details please refer to: http://www.datatables.net 21789 */ 21790 21791/** 21792 * Switch the key value pairing of an index array to be value key (i.e. the old value is now the 21793 * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. 21794 * @method fnInvertKeyValues 21795 * @param array aIn Array to switch around 21796 * @returns array 21797 */ 21798function fnInvertKeyValues( aIn ) 21799{ 21800 var aRet=[]; 21801 for ( var i=0, iLen=aIn.length ; i<iLen ; i++ ) 21802 { 21803 aRet[ aIn[i] ] = i; 21804 } 21805 return aRet; 21806} 21807 21808 21809/** 21810 * Modify an array by switching the position of two elements 21811 * @method fnArraySwitch 21812 * @param array aArray Array to consider, will be modified by reference (i.e. no return) 21813 * @param int iFrom From point 21814 * @param int iTo Insert point 21815 * @returns void 21816 */ 21817function fnArraySwitch( aArray, iFrom, iTo ) 21818{ 21819 var mStore = aArray.splice( iFrom, 1 )[0]; 21820 aArray.splice( iTo, 0, mStore ); 21821} 21822 21823 21824/** 21825 * Switch the positions of nodes in a parent node (note this is specifically designed for 21826 * table rows). Note this function considers all element nodes under the parent! 21827 * @method fnDomSwitch 21828 * @param string sTag Tag to consider 21829 * @param int iFrom Element to move 21830 * @param int Point to element the element to (before this point), can be null for append 21831 * @returns void 21832 */ 21833function fnDomSwitch( nParent, iFrom, iTo ) 21834{ 21835 var anTags = []; 21836 for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ ) 21837 { 21838 if ( nParent.childNodes[i].nodeType == 1 ) 21839 { 21840 anTags.push( nParent.childNodes[i] ); 21841 } 21842 } 21843 var nStore = anTags[ iFrom ]; 21844 21845 if ( iTo !== null ) 21846 { 21847 nParent.insertBefore( nStore, anTags[iTo] ); 21848 } 21849 else 21850 { 21851 nParent.appendChild( nStore ); 21852 } 21853} 21854 21855 21856/** 21857 * Plug-in for DataTables which will reorder the internal column structure by taking the column 21858 * from one position (iFrom) and insert it into a given point (iTo). 21859 * @method $.fn.dataTableExt.oApi.fnColReorder 21860 * @param object oSettings DataTables settings object - automatically added by DataTables! 21861 * @param int iFrom Take the column to be repositioned from this point 21862 * @param int iTo and insert it into this point 21863 * @param bool drop Indicate if the reorder is the final one (i.e. a drop) 21864 * not a live reorder 21865 * @param bool invalidateRows speeds up processing if false passed 21866 * @returns void 21867 */ 21868$.fn.dataTableExt.oApi.fnColReorder = function ( oSettings, iFrom, iTo, drop, invalidateRows ) 21869{ 21870 var i, iLen, j, jLen, jen, iCols=oSettings.aoColumns.length, nTrs, oCol; 21871 var attrMap = function ( obj, prop, mapping ) { 21872 if ( ! obj[ prop ] || typeof obj[ prop ] === 'function' ) { 21873 return; 21874 } 21875 21876 var a = obj[ prop ].split('.'); 21877 var num = a.shift(); 21878 21879 if ( isNaN( num*1 ) ) { 21880 return; 21881 } 21882 21883 obj[ prop ] = mapping[ num*1 ]+'.'+a.join('.'); 21884 }; 21885 21886 /* Sanity check in the input */ 21887 if ( iFrom == iTo ) 21888 { 21889 /* Pointless reorder */ 21890 return; 21891 } 21892 21893 if ( iFrom < 0 || iFrom >= iCols ) 21894 { 21895 this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); 21896 return; 21897 } 21898 21899 if ( iTo < 0 || iTo >= iCols ) 21900 { 21901 this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); 21902 return; 21903 } 21904 21905 /* 21906 * Calculate the new column array index, so we have a mapping between the old and new 21907 */ 21908 var aiMapping = []; 21909 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 21910 { 21911 aiMapping[i] = i; 21912 } 21913 fnArraySwitch( aiMapping, iFrom, iTo ); 21914 var aiInvertMapping = fnInvertKeyValues( aiMapping ); 21915 21916 21917 /* 21918 * Convert all internal indexing to the new column order indexes 21919 */ 21920 /* Sorting */
21921 for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ ) 21922 { 21923 oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ]; 21924 } 21925 21926 /* Fixed sorting */ 21927 if ( oSettings.aaSortingFixed !== null ) 21928 { 21929 for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ ) 21930 { 21931 oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ]; 21932 } 21933 } 21934 21935 /* Data column sorting (the column which the sort for a given column should take place on) */ 21936 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 21937 { 21938 oCol = oSettings.aoColumns[i]; 21939 for ( j=0, jLen=oCol.aDataSort.length ; j<jLen ; j++ ) 21940 { 21941 oCol.aDataSort[j] = aiInvertMapping[ oCol.aDataSort[j] ]; 21942 } 21943 21944 // Update the column indexes 21945 oCol.idx = aiInvertMapping[ oCol.idx ]; 21946 } 21947 21948 // Update 1.10 optimised sort class removal variable 21949 $.each( oSettings.aLastSort, function (i, val) { 21950 oSettings.aLastSort[i].src = aiInvertMapping[ val.src ]; 21951 } ); 21952 21953 /* Update the Get and Set functions for each column */ 21954 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 21955 { 21956 oCol = oSettings.aoColumns[i]; 21957 21958 if ( typeof oCol.mData == 'number' ) { 21959 oCol.mData = aiInvertMapping[ oCol.mData ]; 21960 } 21961 else if ( $.isPlainObject( oCol.mData ) ) { 21962 // HTML5 data sourced 21963 attrMap( oCol.mData, '_', aiInvertMapping ); 21964 attrMap( oCol.mData, 'filter', aiInvertMapping ); 21965 attrMap( oCol.mData, 'sort', aiInvertMapping ); 21966 attrMap( oCol.mData, 'type', aiInvertMapping ); 21967 } 21968 } 21969 21970 /* 21971 * Move the DOM elements 21972 */ 21973 if ( oSettings.aoColumns[iFrom].bVisible ) 21974 { 21975 /* Calculate the current visible index and the point to insert the node before. The insert 21976 * before needs to take into account that there might not be an element to insert before, 21977 * in which case it will be null, and an appendChild should be used 21978 */ 21979 var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom ); 21980 var iInsertBeforeIndex = null; 21981 21982 i = iTo < iFrom ? iTo : iTo + 1; 21983 while ( iInsertBeforeIndex === null && i < iCols ) 21984 { 21985 iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i ); 21986 i++; 21987 } 21988 21989 /* Header */ 21990 nTrs = oSettings.nTHead.getElementsByTagName('tr'); 21991 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 21992 { 21993 fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); 21994 } 21995 21996 /* Footer */ 21997 if ( oSettings.nTFoot !== null ) 21998 { 21999 nTrs = oSettings.nTFoot.getElementsByTagName('tr'); 22000 for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) 22001 { 22002 fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); 22003 } 22004 } 22005 22006 /* Body */ 22007 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) 22008 { 22009 if ( oSettings.aoData[i].nTr !== null ) 22010 { 22011 fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex ); 22012 } 22013 } 22014 } 22015 22016 /* 22017 * Move the internal array elements 22018 */ 22019 /* Columns */ 22020 fnArraySwitch( oSettings.aoColumns, iFrom, iTo ); 22021 22022 // regenerate the get / set functions 22023 for ( i=0, iLen=iCols ; i<iLen ; i++ ) { 22024 oSettings.oApi._fnColumnOptions( oSettings, i, {} ); 22025 } 22026 22027 /* Search columns */ 22028 fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo ); 22029 22030 /* Array array - internal data anodes cache */ 22031 for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) 22032 { 22033 var data = oSettings.aoData[i]; 22034 var cells = data.anCells; 22035 22036 if ( cells ) { 22037 fnArraySwitch( cells, iFrom, iTo ); 22038 22039 // Longer term, should this be moved into the DataTables' invalidate 22040 // methods? 22041 for ( j=0, jen=cells.length ; j<jen ; j++ ) { 22042 if ( cells[j] && cells[j]._DT_CellIndex ) { 22043 cells[j]._DT_CellIndex.column = j; 22044 } 22045 } 22046 } 22047 22048 // Swap around array sourced data (object based is left as is) 22049 if ( Array.isArray( data._aData ) ) { 22050 fnArraySwitch( data._aData, iFrom, iTo ); 22051 } 22052 } 22053 22054 /* Reposition the header elements in the header layout array */ 22055 for ( i=0, iLen=oSettings.aoHeader.length ; i<iLen ; i++ ) 22056 { 22057 fnArraySwitch( oSettings.aoHeader[i], iFrom, iTo ); 22058 } 22059 22060 if ( oSettings.aoFooter !== null ) 22061 { 22062 for ( i=0, iLen=oSettings.aoFooter.length ; i<iLen ; i++ ) 22063 { 22064 fnArraySwitch( oSettings.aoFooter[i], iFrom, iTo ); 22065 } 22066 } 22067 22068 if ( invalidateRows || invalidateRows === undefined ) 22069 { 22070 // Always read from the data object rather than reading back from the DOM 22071 // since it could have been changed by a renderer 22072 $.fn.dataTable.Api( oSettings ).rows().invalidate('data'); 22073 } 22074 22075 /* 22076 * Update DataTables' event handlers 22077 */ 22078 22079 /* Sort listener */ 22080 for ( i=0, iLen=iCols ; i<iLen ; i++ ) 22081 { 22082 $(oSettings.aoColumns[i].nTh).off('.DT'); 22083 this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i ); 22084 } 22085 22086 22087 /* Fire an event so other plug-ins can update */ 22088 $(oSettings.oInstance).trigger( 'column-reorder.dt', [ oSettings, { 22089 from: iFrom, 22090 to: iTo, 22091 mapping: aiInvertMapping, 22092 drop: drop, 22093 22094 // Old style parameters for compatibility 22095 iFrom: iFrom, 22096 iTo: iTo, 22097 aiInvertMapping: aiInvertMapping 22098 } ] ); 22099};
22100 22101/** 22102 * ColReorder provides column visibility control for DataTables 22103 * @class ColReorder 22104 * @constructor 22105 * @param {object} dt DataTables settings object 22106 * @param {object} opts ColReorder options 22107 */ 22108var ColReorder = function( dt, opts ) 22109{ 22110 var settings = new $.fn.dataTable.Api( dt ).settings()[0]; 22111 22112 // Ensure that we can't initialise on the same table twice 22113 if ( settings._colReorder ) { 22114 return settings._colReorder; 22115 } 22116 22117 // Allow the options to be a boolean for defaults 22118 if ( opts === true ) { 22119 opts = {}; 22120 } 22121 22122 // Convert from camelCase to Hungarian, just as DataTables does 22123 var camelToHungarian = $.fn.dataTable.camelToHungarian; 22124 if ( camelToHungarian ) { 22125 camelToHungarian( ColReorder.defaults, ColReorder.defaults, true ); 22126 camelToHungarian( ColReorder.defaults, opts || {} ); 22127 } 22128 22129 22130 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 22131 * Public class variables 22132 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 22133 22134 /** 22135 * @namespace Settings object which contains customisable information for ColReorder instance 22136 */ 22137 this.s = { 22138 /** 22139 * DataTables settings object 22140 * @property dt 22141 * @type Object 22142 * @default null 22143 */ 22144 "dt": null, 22145 22146 /** 22147 * Enable flag 22148 * @property dt 22149 * @type Object 22150 * @default null 22151 */ 22152 "enable": null, 22153 22154 /** 22155 * Initialisation object used for this instance 22156 * @property init 22157 * @type object 22158 * @default {} 22159 */ 22160 "init": $.extend( true, {}, ColReorder.defaults, opts ), 22161 22162 /** 22163 * Number of columns to fix (not allow to be reordered) 22164 * @property fixed 22165 * @type int 22166 * @default 0 22167 */ 22168 "fixed": 0, 22169 22170 /** 22171 * Number of columns to fix counting from right (not allow to be reordered) 22172 * @property fixedRight 22173 * @type int 22174 * @default 0 22175 */ 22176 "fixedRight": 0, 22177 22178 /** 22179 * Callback function for once the reorder has been done 22180 * @property reorderCallback 22181 * @type function 22182 * @default null 22183 */ 22184 "reorderCallback": null, 22185 22186 /** 22187 * @namespace Information used for the mouse drag 22188 */ 22189 "mouse": { 22190 "startX": -1, 22191 "startY": -1, 22192 "offsetX": -1, 22193 "offsetY": -1, 22194 "target": -1, 22195 "targetIndex": -1, 22196 "fromIndex": -1 22197 }, 22198 22199 /** 22200 * Information which is used for positioning the insert cusor and knowing where to do the 22201 * insert. Array of objects with the properties: 22202 * x: x-axis position 22203 * to: insert point 22204 * @property aoTargets 22205 * @type array 22206 * @default [] 22207 */ 22208 "aoTargets": [] 22209 }; 22210 22211 22212 /** 22213 * @namespace Common and useful DOM elements for the class instance 22214 */ 22215 this.dom = { 22216 /** 22217 * Dragging element (the one the mouse is moving) 22218 * @property drag 22219 * @type element 22220 * @default null 22221 */ 22222 "drag": null, 22223 22224 /** 22225 * The insert cursor 22226 * @property pointer 22227 * @type element 22228 * @default null 22229 */ 22230 "pointer": null 22231 }; 22232 22233 /* Constructor logic */ 22234 this.s.enable = this.s.init.bEnable; 22235 this.s.dt = settings; 22236 this.s.dt._colReorder = this; 22237 this._fnConstruct(); 22238 22239 return this; 22240}; 22241 22242 22243 22244$.extend( ColReorder.prototype, { 22245 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 22246 * Public methods 22247 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 22248 22249 /** 22250 * Enable / disable end user interaction 22251 */ 22252 fnEnable: function ( flag ) 22253 { 22254 if ( flag === false ) { 22255 return this.fnDisable(); 22256 } 22257 22258 this.s.enable = true; 22259 }, 22260 22261 /** 22262 * Disable end user interaction 22263 */ 22264 fnDisable: function () 22265 { 22266 this.s.enable = false; 22267 }, 22268 22269 /** 22270 * Reset the column ordering to the original ordering that was detected on 22271 * start up. 22272 * @return {this} Returns `this` for chaining. 22273 * 22274 * @example 22275 * // DataTables initialisation with ColReorder 22276 * var table = $('#example').dataTable( { 22277 * "sDom": 'Rlfrtip' 22278 * } ); 22279 * 22280 * // Add click event to a button to reset the ordering 22281 * $('#resetOrdering').click( function (e) { 22282 * e.preventDefault(); 22283 * $.fn.dataTable.ColReorder( table ).fnReset(); 22284 * } ); 22285 */ 22286 "fnReset": function () 22287 { 22288 this._fnOrderColumns( this.fnOrder() ); 22289 22290 return this; 22291 }, 22292 22293 /** 22294 * `Deprecated` - Get the current order of the columns, as an array. 22295 * @return {array} Array of column identifiers 22296 * @deprecated `fnOrder` should be used in preference to this method. 22297 * `fnOrder` acts as a getter/setter. 22298 */ 22299 "fnGetCurrentOrder": function () 22300 { 22301 return this.fnOrder(); 22302 }, 22303 22304 /** 22305 * Get the current order of the columns, as an array. Note that the values 22306 * given in the array are unique identifiers for each column. Currently 22307 * these are the original ordering of the columns that was detected on 22308 * start up, but this could potentially change in future. 22309 * @return {array} Array of column identifiers 22310 * 22311 * @example 22312 * // Get column ordering for the table 22313 * var order = $.fn.dataTable.ColReorder( dataTable ).fnOrder(); 22314 *//** 22315 * Set the order of the columns, from the positions identified in the 22316 * ordering array given. Note that ColReorder takes a brute force approach 22317 * to reordering, so it is possible multiple reordering events will occur 22318 * before the final order is settled upon. 22319 * @param {array} [set] Array of column identifiers in the new order. Note 22320 * that every column must be included, uniquely, in this array. 22321 * @return {this} Returns `this` for chaining. 22322 * 22323 * @example 22324 * // Swap the first and second columns 22325 * $.fn.dataTable.ColReorder( dataTable ).fnOrder( [1, 0, 2, 3, 4] ); 22326 * 22327 * @example 22328 * // Move the first column to the end for the table `#example` 22329 * var curr = $.fn.dataTable.ColReorder( '#example' ).fnOrder(); 22330 * var first = curr.shift();
22331 * curr.push( first ); 22332 * $.fn.dataTable.ColReorder( '#example' ).fnOrder( curr ); 22333 * 22334 * @example 22335 * // Reverse the table's order 22336 * $.fn.dataTable.ColReorder( '#example' ).fnOrder( 22337 * $.fn.dataTable.ColReorder( '#example' ).fnOrder().reverse() 22338 * ); 22339 */ 22340 "fnOrder": function ( set, original ) 22341 { 22342 var a = [], i, ien, j, jen; 22343 var columns = this.s.dt.aoColumns; 22344 22345 if ( set === undefined ){ 22346 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 22347 a.push( columns[i]._ColReorder_iOrigCol ); 22348 } 22349 22350 return a; 22351 } 22352 22353 // The order given is based on the original indexes, rather than the 22354 // existing ones, so we need to translate from the original to current 22355 // before then doing the order 22356 if ( original ) { 22357 var order = this.fnOrder(); 22358 22359 for ( i=0, ien=set.length ; i<ien ; i++ ) { 22360 a.push( $.inArray( set[i], order ) ); 22361 } 22362 22363 set = a; 22364 } 22365 22366 this._fnOrderColumns( fnInvertKeyValues( set ) ); 22367 22368 return this; 22369 }, 22370 22371 22372 /** 22373 * Convert from the original column index, to the original 22374 * 22375 * @param {int|array} idx Index(es) to convert 22376 * @param {string} dir Transpose direction - `fromOriginal` / `toCurrent` 22377 * or `'toOriginal` / `fromCurrent` 22378 * @return {int|array} Converted values 22379 */ 22380 fnTranspose: function ( idx, dir ) 22381 { 22382 if ( ! dir ) { 22383 dir = 'toCurrent'; 22384 } 22385 22386 var order = this.fnOrder(); 22387 var columns = this.s.dt.aoColumns; 22388 22389 if ( dir === 'toCurrent' ) { 22390 // Given an original index, want the current 22391 return ! Array.isArray( idx ) ? 22392 $.inArray( idx, order ) : 22393 $.map( idx, function ( index ) { 22394 return $.inArray( index, order ); 22395 } ); 22396 } 22397 else { 22398 // Given a current index, want the original 22399 return ! Array.isArray( idx ) ? 22400 columns[idx]._ColReorder_iOrigCol : 22401 $.map( idx, function ( index ) { 22402 return columns[index]._ColReorder_iOrigCol; 22403 } ); 22404 } 22405 }, 22406 22407 22408 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 22409 * Private methods (they are of course public in JS, but recommended as private) 22410 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 22411 22412 /** 22413 * Constructor logic 22414 * @method _fnConstruct 22415 * @returns void 22416 * @private 22417 */ 22418 "_fnConstruct": function () 22419 { 22420 var that = this; 22421 var iLen = this.s.dt.aoColumns.length; 22422 var table = this.s.dt.nTable; 22423 var i; 22424 22425 /* Columns discounted from reordering - counting left to right */ 22426 if ( this.s.init.iFixedColumns ) 22427 { 22428 this.s.fixed = this.s.init.iFixedColumns; 22429 } 22430 22431 if ( this.s.init.iFixedColumnsLeft ) 22432 { 22433 this.s.fixed = this.s.init.iFixedColumnsLeft; 22434 } 22435 22436 /* Columns discounted from reordering - counting right to left */ 22437 this.s.fixedRight = this.s.init.iFixedColumnsRight ? 22438 this.s.init.iFixedColumnsRight : 22439 0; 22440 22441 /* Drop callback initialisation option */ 22442 if ( this.s.init.fnReorderCallback ) 22443 { 22444 this.s.reorderCallback = this.s.init.fnReorderCallback; 22445 } 22446 22447 /* Add event handlers for the drag and drop, and also mark the original column order */ 22448 for ( i = 0; i < iLen; i++ ) 22449 { 22450 if ( i > this.s.fixed-1 && i < iLen - this.s.fixedRight ) 22451 { 22452 this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); 22453 } 22454 22455 /* Mark the original column order for later reference */ 22456 this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; 22457 } 22458 22459 /* State saving */ 22460 this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { 22461 that._fnStateSave.call( that, oData ); 22462 }, "ColReorder_State" ); 22463 22464 this.s.dt.oApi._fnCallbackReg(this.s.dt, 'aoStateLoadParams', function(oS, oData) { 22465 that.s.dt._colReorder.fnOrder(oData.ColReorder, true); 22466 }) 22467 22468 /* An initial column order has been specified */ 22469 var aiOrder = null; 22470 if ( this.s.init.aiOrder ) 22471 { 22472 aiOrder = this.s.init.aiOrder.slice(); 22473 } 22474 22475 /* State loading, overrides the column order given */ 22476 if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && 22477 this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) 22478 { 22479 aiOrder = this.s.dt.oLoadedState.ColReorder; 22480 } 22481 22482 /* If we have an order to apply - do so */ 22483 if ( aiOrder ) 22484 { 22485 /* We might be called during or after the DataTables initialisation. If before, then we need 22486 * to wait until the draw is done, if after, then do what we need to do right away 22487 */ 22488 if ( !that.s.dt._bInitComplete ) 22489 { 22490 var bDone = false; 22491 $(table).on( 'draw.dt.colReorder', function () { 22492 if ( !that.s.dt._bInitComplete && !bDone ) 22493 { 22494 bDone = true; 22495 var resort = fnInvertKeyValues( aiOrder ); 22496 that._fnOrderColumns.call( that, resort ); 22497 } 22498 } ); 22499 } 22500 else 22501 { 22502 var resort = fnInvertKeyValues( aiOrder ); 22503 that._fnOrderColumns.call( that, resort ); 22504 } 22505 } 22506 else { 22507 this._fnSetColumnIndexes(); 22508 } 22509 22510 // Destroy clean up 22511 $(table).on( 'destroy.dt.colReorder', function () { 22512 // Restore table to original order from when it was loaded 22513 that.fnReset(); 22514 22515 $(table).off( 'destroy.dt.colReorder draw.dt.colReorder' ); 22516
22517 $.each( that.s.dt.aoColumns, function (i, column) { 22518 $(column.nTh).off('.ColReorder'); 22519 $(column.nTh).removeAttr('data-column-index'); 22520 } ); 22521 22522 that.s.dt._colReorder = null; 22523 that.s = null; 22524 } ); 22525 }, 22526 22527 22528 /** 22529 * Set the column order from an array 22530 * @method _fnOrderColumns 22531 * @param array a An array of integers which dictate the column order that should be applied 22532 * @returns void 22533 * @private 22534 */ 22535 "_fnOrderColumns": function ( a ) 22536 { 22537 var changed = false; 22538 22539 if ( a.length != this.s.dt.aoColumns.length ) 22540 { 22541 this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+ 22542 "match known number of columns. Skipping." ); 22543 return; 22544 } 22545 22546 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) 22547 { 22548 var currIndex = $.inArray( i, a ); 22549 if ( i != currIndex ) 22550 { 22551 /* Reorder our switching array */ 22552 fnArraySwitch( a, currIndex, i ); 22553 22554 /* Do the column reorder in the table */ 22555 this.s.dt.oInstance.fnColReorder( currIndex, i, true, false ); 22556 22557 changed = true; 22558 } 22559 } 22560 22561 this._fnSetColumnIndexes(); 22562 22563 // Has anything actually changed? If not, then nothing else to do 22564 if ( ! changed ) { 22565 return; 22566 } 22567 22568 $.fn.dataTable.Api( this.s.dt ).rows().invalidate('data'); 22569 22570 /* When scrolling we need to recalculate the column sizes to allow for the shift */ 22571 if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) 22572 { 22573 this.s.dt.oInstance.fnAdjustColumnSizing( false ); 22574 } 22575 22576 /* Save the state */ 22577 this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); 22578 22579 if ( this.s.reorderCallback !== null ) 22580 { 22581 this.s.reorderCallback.call( this ); 22582 } 22583 }, 22584 22585 22586 /** 22587 * Because we change the indexes of columns in the table, relative to their starting point 22588 * we need to reorder the state columns to what they are at the starting point so we can 22589 * then rearrange them again on state load! 22590 * @method _fnStateSave 22591 * @param object oState DataTables state 22592 * @returns string JSON encoded cookie string for DataTables 22593 * @private 22594 */ 22595 "_fnStateSave": function ( oState ) 22596 { 22597 if(this.s === null) { 22598 return; 22599 } 22600 var i, iLen, aCopy, iOrigColumn; 22601 var oSettings = this.s.dt; 22602 var columns = oSettings.aoColumns; 22603 22604 oState.ColReorder = []; 22605 22606 /* Sorting */ 22607 if ( oState.aaSorting ) { 22608 // 1.10.0- 22609 for ( i=0 ; i<oState.aaSorting.length ; i++ ) { 22610 oState.aaSorting[i][0] = columns[ oState.aaSorting[i][0] ]._ColReorder_iOrigCol; 22611 } 22612 22613 var aSearchCopy = $.extend( true, [], oState.aoSearchCols ); 22614 22615 for ( i=0, iLen=columns.length ; i<iLen ; i++ ) 22616 { 22617 iOrigColumn = columns[i]._ColReorder_iOrigCol; 22618 22619 /* Column filter */ 22620 oState.aoSearchCols[ iOrigColumn ] = aSearchCopy[i]; 22621 22622 /* Visibility */ 22623 oState.abVisCols[ iOrigColumn ] = columns[i].bVisible; 22624 22625 /* Column reordering */ 22626 oState.ColReorder.push( iOrigColumn ); 22627 } 22628 } 22629 else if ( oState.order ) { 22630 // 1.10.1+ 22631 for ( i=0 ; i<oState.order.length ; i++ ) { 22632 oState.order[i][0] = columns[ oState.order[i][0] ]._ColReorder_iOrigCol; 22633 } 22634 22635 var stateColumnsCopy = $.extend( true, [], oState.columns ); 22636 22637 for ( i=0, iLen=columns.length ; i<iLen ; i++ ) 22638 { 22639 iOrigColumn = columns[i]._ColReorder_iOrigCol; 22640 22641 /* Columns */ 22642 oState.columns[ iOrigColumn ] = stateColumnsCopy[i]; 22643 22644 /* Column reordering */ 22645 oState.ColReorder.push( iOrigColumn ); 22646 } 22647 } 22648 }, 22649 22650 22651 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 22652 * Mouse drop and drag 22653 */ 22654 22655 /** 22656 * Add a mouse down listener to a particluar TH element 22657 * @method _fnMouseListener 22658 * @param int i Column index 22659 * @param element nTh TH element clicked on 22660 * @returns void 22661 * @private 22662 */ 22663 "_fnMouseListener": function ( i, nTh ) 22664 { 22665 var that = this; 22666 $(nTh) 22667 .on( 'mousedown.ColReorder', function (e) { 22668 if ( that.s.enable && e.which === 1 ) { 22669 that._fnMouseDown.call( that, e, nTh ); 22670 } 22671 } ) 22672 .on( 'touchstart.ColReorder', function (e) { 22673 if ( that.s.enable ) { 22674 that._fnMouseDown.call( that, e, nTh ); 22675 } 22676 } ); 22677 }, 22678 22679 22680 /** 22681 * Mouse down on a TH element in the table header 22682 * @method _fnMouseDown 22683 * @param event e Mouse event 22684 * @param element nTh TH element to be dragged 22685 * @returns void 22686 * @private 22687 */ 22688 "_fnMouseDown": function ( e, nTh ) 22689 { 22690 var that = this; 22691 22692 /* Store information about the mouse position */ 22693 var target = $(e.target).closest('th, td'); 22694 var offset = target.offset(); 22695 var idx = parseInt( $(nTh).attr('data-column-index'), 10 ); 22696 22697 if ( idx === undefined ) { 22698 return; 22699 } 22700 22701 this.s.mouse.startX = this._fnCursorPosition( e, 'pageX' ); 22702 this.s.mouse.startY = this._fnCursorPosition( e, 'pageY' ); 22703 this.s.mouse.offsetX = this._fnCursorPosition( e, 'pageX' ) - offset.left; 22704 this.s.mouse.offsetY = this._fnCursorPosition( e, 'pageY' ) - offset.top; 22705 this.s.mouse.target = this.s.dt.aoColumns[ idx ].nTh;//target[0]; 22706 this.s.mouse.targetIndex = idx; 22707 this.s.mouse.fromIndex = idx; 22708 22709 this._fnRegions(); 22710 22711 /* Add event handlers to the document */ 22712 $(document) 22713 .on( 'mousemove.ColReorder touchmove.ColReorder', function (e) { 22714 that._fnMouseMove.call( that, e ); 22715 } ) 22716 .on( 'mouseup.ColReorder touchend.ColReorder', function (e) { 22717 that._fnMouseUp.call( that, e ); 22718 } ); 22719 }, 22720 22721 22722 /** 22723 * Deal with a mouse move event while dragging a node 22724 * @method _fnMouseMove 22725 * @param event e Mouse event 22726 * @returns void 22727 * @private 22728 */ 22729 "_fnMouseMove": function ( e ) 22730 { 22731 var that = this; 22732 22733 if ( this.dom.drag === null ) 22734 { 22735 /* Only create the drag element if the mouse has moved a specific distance from the start 22736 * point - this allows the user to make small mouse movements when sorting and not have a 22737 * possibly confusing drag element showing up 22738 */ 22739 if ( Math.pow( 22740 Math.pow(this._fnCursorPosition( e, 'pageX') - this.s.mouse.startX, 2) + 22741 Math.pow(this._fnCursorPosition( e, 'pageY') - this.s.mouse.startY, 2), 0.5 ) < 5 ) 22742 { 22743 return; 22744 } 22745 this._fnCreateDragNode(); 22746 } 22747 22748 /* Position the element - we respect where in the element the click occured */ 22749 this.dom.drag.css( { 22750 left: this._fnCursorPosition( e, 'pageX' ) - this.s.mouse.offsetX, 22751 top: this._fnCursorPosition( e, 'pageY' ) - this.s.mouse.offsetY 22752 } ); 22753 22754 /* Based on the current mouse position, calculate where the insert should go */ 22755 var target; 22756 var lastToIndex = this.s.mouse.toIndex; 22757 var cursorXPosiotion = this._fnCursorPosition(e, 'pageX'); 22758 var targetsPrev = function (i) { 22759 while (i >= 0) { 22760 i--; 22761 22762 if (i <= 0) { 22763 return null; 22764 } 22765
22766 if (that.s.aoTargets[i+1].x !== that.s.aoTargets[i].x) { 22767 return that.s.aoTargets[i]; 22768 } 22769 } 22770 }; 22771 var firstNotHidden = function () { 22772 for (var i=0 ; i<that.s.aoTargets.length-1 ; i++) { 22773 if (that.s.aoTargets[i].x !== that.s.aoTargets[i+1].x) { 22774 return that.s.aoTargets[i]; 22775 } 22776 } 22777 }; 22778 var lastNotHidden = function () { 22779 for (var i=that.s.aoTargets.length-1 ; i>0 ; i--) { 22780 if (that.s.aoTargets[i].x !== that.s.aoTargets[i-1].x) { 22781 return that.s.aoTargets[i]; 22782 } 22783 } 22784 }; 22785 22786 for (var i = 1; i < this.s.aoTargets.length; i++) { 22787 var prevTarget = targetsPrev(i); 22788 if (! prevTarget) { 22789 prevTarget = firstNotHidden(); 22790 } 22791 22792 var prevTargetMiddle = prevTarget.x + (this.s.aoTargets[i].x - prevTarget.x) / 2; 22793 22794 if (this._fnIsLtr()) { 22795 if (cursorXPosiotion < prevTargetMiddle ) { 22796 target = prevTarget; 22797 break; 22798 } 22799 } 22800 else { 22801 if (cursorXPosiotion > prevTargetMiddle) { 22802 target = prevTarget; 22803 break; 22804 } 22805 } 22806 } 22807 22808 if (target) { 22809 this.dom.pointer.css('left', target.x); 22810 this.s.mouse.toIndex = target.to; 22811 } 22812 else { 22813 // The insert element wasn't positioned in the array (less than 22814 // operator), so we put it at the end 22815 this.dom.pointer.css( 'left', lastNotHidden().x ); 22816 this.s.mouse.toIndex = lastNotHidden().to; 22817 } 22818 22819 // Perform reordering if realtime updating is on and the column has moved 22820 if ( this.s.init.bRealtime && lastToIndex !== this.s.mouse.toIndex ) { 22821 this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex ); 22822 this.s.mouse.fromIndex = this.s.mouse.toIndex; 22823 22824 // Not great for performance, but required to keep everything in alignment 22825 if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) 22826 { 22827 this.s.dt.oInstance.fnAdjustColumnSizing( false ); 22828 } 22829 22830 this._fnRegions(); 22831 } 22832 }, 22833 22834 22835 /** 22836 * Finish off the mouse drag and insert the column where needed 22837 * @method _fnMouseUp 22838 * @param event e Mouse event 22839 * @returns void 22840 * @private 22841 */ 22842 "_fnMouseUp": function ( e ) 22843 { 22844 var that = this; 22845 22846 $(document).off( '.ColReorder' ); 22847 22848 if ( this.dom.drag !== null ) 22849 { 22850 /* Remove the guide elements */ 22851 this.dom.drag.remove(); 22852 this.dom.pointer.remove(); 22853 this.dom.drag = null; 22854 this.dom.pointer = null; 22855 22856 /* Actually do the reorder */ 22857 this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex, true ); 22858 this._fnSetColumnIndexes(); 22859 22860 /* When scrolling we need to recalculate the column sizes to allow for the shift */ 22861 if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) 22862 { 22863 this.s.dt.oInstance.fnAdjustColumnSizing( false ); 22864 } 22865 22866 /* Save the state */ 22867 this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); 22868 22869 if ( this.s.reorderCallback !== null ) 22870 { 22871 this.s.reorderCallback.call( this ); 22872 } 22873 } 22874 }, 22875 22876 22877 /** 22878 * Calculate a cached array with the points of the column inserts, and the 22879 * 'to' points 22880 * @method _fnRegions 22881 * @returns void 22882 * @private 22883 */ 22884 "_fnRegions": function () 22885 { 22886 var aoColumns = this.s.dt.aoColumns; 22887 var isLTR = this._fnIsLtr(); 22888 this.s.aoTargets.splice(0, this.s.aoTargets.length); 22889 var lastBound = $(this.s.dt.nTable).offset().left; 22890 22891 var aoColumnBounds = []; 22892 $.each(aoColumns, function (i, column) { 22893 if (column.bVisible && column.nTh.style.display !== 'none') { 22894 var nth = $(column.nTh); 22895 var bound = nth.offset().left; 22896 22897 if (isLTR) { 22898 bound += nth.outerWidth(); 22899 } 22900 22901 aoColumnBounds.push({ 22902 index: i, 22903 bound: bound 22904 }); 22905 22906 lastBound = bound; 22907 } 22908 else { 22909 aoColumnBounds.push({ 22910 index: i, 22911 bound: lastBound 22912 }); 22913 } 22914 }); 22915 22916 var firstColumn = aoColumnBounds[0]; 22917 var firstColumnWidth = $(aoColumns[firstColumn.index].nTh).outerWidth(); 22918 22919 this.s.aoTargets.push({ 22920 to: 0, 22921 x: firstColumn.bound - firstColumnWidth 22922 }); 22923 22924 for (var i = 0; i < aoColumnBounds.length; i++) { 22925 var columnBound = aoColumnBounds[i]; 22926 var iToPoint = columnBound.index; 22927 22928 /* For the column / header in question, we want it's position to remain the same if the 22929 * position is just to it's immediate left or right, so we only increment the counter for 22930 * other columns 22931 */ 22932 if (columnBound.index < this.s.mouse.fromIndex) { 22933 iToPoint++; 22934 } 22935 22936 this.s.aoTargets.push({ 22937 to: iToPoint, 22938 x: columnBound.bound 22939 }); 22940 } 22941 22942 /* Disallow columns for being reordered by drag and drop, counting right to left */ 22943 if ( this.s.fixedRight !== 0 ) 22944 { 22945 this.s.aoTargets.splice( this.s.aoTargets.length - this.s.fixedRight ); 22946 } 22947 22948 /* Disallow columns for being reordered by drag and drop, counting left to right */ 22949 if ( this.s.fixed !== 0 ) 22950 { 22951 this.s.aoTargets.splice( 0, this.s.fixed ); 22952 } 22953 }, 22954 22955 22956 /** 22957 * Copy the TH element that is being drags so the user has the idea that they are actually 22958 * moving it around the page. 22959 * @method _fnCreateDragNode 22960 * @returns void 22961 * @private 22962 */ 22963 "_fnCreateDragNode": function () 22964 { 22965 var scrolling = this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== ""; 22966 22967 var origCell = this.s.dt.aoColumns[ this.s.mouse.targetIndex ].nTh; 22968 var origTr = origCell.parentNode; 22969 var origThead = origTr.parentNode; 22970 var origTable = origThead.parentNode; 22971 var cloneCell = $(origCell).clone(); 22972 22973 // This is a slightly odd combination of jQuery and DOM, but it is the 22974 // fastest and least resource intensive way I could think of cloning 22975 // the table with just a single header cell in it. 22976 this.dom.drag = $(origTable.cloneNode(false)) 22977 .addClass( 'DTCR_clonedTable' ) 22978 .append( 22979 $(origThead.cloneNode(false)).append( 22980 $(origTr.cloneNode(false)).append( 22981 cloneCell[0] 22982 ) 22983 ) 22984 ) 22985 .css( { 22986 position: 'absolute', 22987 top: 0, 22988 left: 0, 22989 width: $(origCell).outerWidth(), 22990 height: $(origCell).outerHeight() 22991 } ) 22992 .appendTo( 'body' ); 22993
22994 this.dom.pointer = $('<div></div>') 22995 .addClass( 'DTCR_pointer' ) 22996 .css( { 22997 position: 'absolute', 22998 top: scrolling ? 22999 $($(this.s.dt.nScrollBody).parent()).offset().top : 23000 $(this.s.dt.nTable).offset().top, 23001 height : scrolling ? 23002 $($(this.s.dt.nScrollBody).parent()).height() : 23003 $(this.s.dt.nTable).height() 23004 } ) 23005 .appendTo( 'body' ); 23006 }, 23007 23008 23009 /** 23010 * Add a data attribute to the column headers, so we know the index of 23011 * the row to be reordered. This allows fast detection of the index, and 23012 * for this plug-in to work with FixedHeader which clones the nodes. 23013 * @private 23014 */ 23015 "_fnSetColumnIndexes": function () 23016 { 23017 $.each( this.s.dt.aoColumns, function (i, column) { 23018 $(column.nTh).attr('data-column-index', i); 23019 } ); 23020 }, 23021 23022 23023 /** 23024 * Get cursor position regardless of mouse or touch input 23025 * @param {Event} e jQuery Event 23026 * @param {string} prop Property to get 23027 * @return {number} Value 23028 */ 23029 _fnCursorPosition: function ( e, prop ) { 23030 if ( e.type.indexOf('touch') !== -1 ) { 23031 return e.originalEvent.touches[0][ prop ]; 23032 } 23033 return e[ prop ]; 23034 }, 23035 23036 _fnIsLtr: function () { 23037 return $(this.s.dt.nTable).css('direction') !== "rtl"; 23038 } 23039} ); 23040 23041 23042 23043 23044 23045/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 23046 * Static parameters 23047 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 23048 23049 23050/** 23051 * ColReorder default settings for initialisation 23052 * @namespace 23053 * @static 23054 */ 23055ColReorder.defaults = { 23056 /** 23057 * Predefined ordering for the columns that will be applied automatically 23058 * on initialisation. If not specified then the order that the columns are 23059 * found to be in the HTML is the order used. 23060 * @type array 23061 * @default null 23062 * @static 23063 */ 23064 aiOrder: null, 23065 23066 /** 23067 * ColReorder enable on initialisation 23068 * @type boolean 23069 * @default true 23070 * @static 23071 */ 23072 bEnable: true, 23073 23074 /** 23075 * Redraw the table's column ordering as the end user draws the column 23076 * (`true`) or wait until the mouse is released (`false` - default). Note 23077 * that this will perform a redraw on each reordering, which involves an 23078 * Ajax request each time if you are using server-side processing in 23079 * DataTables. 23080 * @type boolean 23081 * @default false 23082 * @static 23083 */ 23084 bRealtime: true, 23085 23086 /** 23087 * Indicate how many columns should be fixed in position (counting from the 23088 * left). This will typically be 1 if used, but can be as high as you like. 23089 * @type int 23090 * @default 0 23091 * @static 23092 */ 23093 iFixedColumnsLeft: 0, 23094 23095 /** 23096 * As `iFixedColumnsRight` but counting from the right. 23097 * @type int 23098 * @default 0 23099 * @static 23100 */ 23101 iFixedColumnsRight: 0, 23102 23103 /** 23104 * Callback function that is fired when columns are reordered. The `column- 23105 * reorder` event is preferred over this callback 23106 * @type function():void 23107 * @default null 23108 * @static 23109 */ 23110 fnReorderCallback: null 23111}; 23112 23113 23114 23115/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 23116 * Constants 23117 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 23118 23119/** 23120 * ColReorder version 23121 * @constant version 23122 * @type String 23123 * @default As code 23124 */ 23125ColReorder.version = "1.6.2"; 23126 23127 23128 23129/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 23130 * DataTables interfaces 23131 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 23132 23133// Expose 23134$.fn.dataTable.ColReorder = ColReorder; 23135$.fn.DataTable.ColReorder = ColReorder; 23136 23137 23138// Register a new feature with DataTables 23139if ( typeof $.fn.dataTable == "function" && 23140 typeof $.fn.dataTableExt.fnVersionCheck == "function" && 23141 $.fn.dataTableExt.fnVersionCheck('1.10.8') ) 23142{ 23143 $.fn.dataTableExt.aoFeatures.push( { 23144 "fnInit": function( settings ) { 23145 var table = settings.oInstance; 23146 23147 if ( ! settings._colReorder ) { 23148 var dtInit = settings.oInit; 23149 var opts = dtInit.colReorder || dtInit.oColReorder || {}; 23150 23151 new ColReorder( settings, opts ); 23152 } 23153 else { 23154 table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" ); 23155 } 23156 23157 return null; /* No node for DataTables to insert */ 23158 }, 23159 "cFeature": "R", 23160 "sFeature": "ColReorder" 23161 } ); 23162} 23163else { 23164 alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download"); 23165} 23166 23167 23168// Attach a listener to the document which listens for DataTables initialisation 23169// events so we can automatically initialise 23170$(document).on( 'preInit.dt.colReorder', function (e, settings) { 23171 if ( e.namespace !== 'dt' ) { 23172 return; 23173 } 23174 23175 var init = settings.oInit.colReorder; 23176 var defaults = DataTable.defaults.colReorder; 23177 23178 if ( init || defaults ) { 23179 var opts = $.extend( {}, init, defaults ); 23180 23181 if ( init !== false ) { 23182 new ColReorder( settings, opts ); 23183 } 23184 } 23185} ); 23186 23187 23188// API augmentation 23189$.fn.dataTable.Api.register( 'colReorder.reset()', function () { 23190 return this.iterator( 'table', function ( ctx ) { 23191 ctx._colReorder.fnReset(); 23192 } ); 23193} ); 23194 23195$.fn.dataTable.Api.register( 'colReorder.order()', function ( set, original ) { 23196 if ( set ) { 23197 return this.iterator( 'table', function ( ctx ) { 23198 ctx._colReorder.fnOrder( set, original ); 23199 } ); 23200 } 23201 23202 return this.context.length ? 23203 this.context[0]._colReorder.fnOrder() : 23204 null; 23205} ); 23206 23207$.fn.dataTable.Api.register( 'colReorder.transpose()', function ( idx, dir ) { 23208 return this.context.length && this.context[0]._colReorder ? 23209 this.context[0]._colReorder.fnTranspose( idx, dir ) : 23210 idx; 23211} ); 23212 23213$.fn.dataTable.Api.register( 'colReorder.move()', function( from, to, drop, invalidateRows ) { 23214 if (this.context.length) { 23215 this.context[0]._colReorder.s.dt.oInstance.fnColReorder( from, to, drop, invalidateRows ); 23216 this.context[0]._colReorder._fnSetColumnIndexes(); 23217 } 23218 return this; 23219} ); 23220 23221$.fn.dataTable.Api.register( 'colReorder.enable()', function( flag ) { 23222 return this.iterator( 'table', function ( ctx ) { 23223 if ( ctx._colReorder ) { 23224 ctx._colReorder.fnEnable( flag ); 23225 } 23226 } ); 23227} ); 23228 23229$.fn.dataTable.Api.register( 'colReorder.disable()', function() { 23230 return this.iterator( 'table', function ( ctx ) { 23231 if ( ctx._colReorder ) { 23232 ctx._colReorder.fnDisable(); 23233 } 23234 } ); 23235} ); 23236 23237 23238return DataTable; 23239})); 23240 23241 23242/*! FixedColumns 4.2.2 23243 * © SpryMedia Ltd - datatables.net/license 23244 */ 23245 23246(function( factory ){ 23247 if ( typeof define === 'function' && define.amd ) { 23248 // AMD 23249 define( ['jquery', 'datatables.net'], function ( $ ) { 23250 return factory( $, window, document ); 23251 } ); 23252 } 23253 else if ( typeof exports === 'object' ) { 23254 // CommonJS 23255 var jq = require('jquery'); 23256 var cjsRequires = function (root, $) { 23257 if ( ! $.fn.dataTable ) { 23258 require('datatables.net')(root, $); 23259 } 23260 }; 23261 23262 if (typeof window !== 'undefined') { 23263 module.exports = function (root, $) { 23264 if ( ! root ) { 23265 // CommonJS environments without a window global must pass a 23266 // root. This will give an error otherwise 23267 root = window; 23268 } 23269 23270 if ( ! $ ) { 23271 $ = jq( root ); 23272 } 23273 23274 cjsRequires( root, $ ); 23275 return factory( $, root, root.document ); 23276 }; 23277 } 23278 else { 23279 cjsRequires( window, jq ); 23280 module.exports = factory( jq, window, window.document ); 23281 } 23282 } 23283 else { 23284 // Browser 23285 factory( jQuery, window, document ); 23286 } 23287}(function( $, window, document, undefined ) { 23288'use strict'; 23289var DataTable = $.fn.dataTable; 23290 23291 23292(function () { 23293 'use strict'; 23294 23295 var $$1; 23296 var dataTable; 23297 function setJQuery(jq) { 23298 $$1 = jq; 23299 dataTable = $$1.fn.dataTable; 23300 } 23301 var FixedColumns = /** @class */ (function () { 23302 function FixedColumns(settings, opts) { 23303 var _this = this;
23304 // Check that the required version of DataTables is included 23305 if (!dataTable || !dataTable.versionCheck || !dataTable.versionCheck('1.10.0')) { 23306 throw new Error('StateRestore requires DataTables 1.10 or newer'); 23307 } 23308 var table = new dataTable.Api(settings); 23309 this.classes = $$1.extend(true, {}, FixedColumns.classes); 23310 // Get options from user 23311 this.c = $$1.extend(true, {}, FixedColumns.defaults, opts); 23312 // Backwards compatibility for deprecated leftColumns 23313 if ((!opts || opts.left === undefined) && this.c.leftColumns !== undefined) { 23314 this.c.left = this.c.leftColumns; 23315 } 23316 // Backwards compatibility for deprecated rightColumns 23317 if ((!opts || opts.right === undefined) && this.c.rightColumns !== undefined) { 23318 this.c.right = this.c.rightColumns; 23319 } 23320 this.s = { 23321 barWidth: 0, 23322 dt: table, 23323 rtl: $$1('body').css('direction') === 'rtl' 23324 }; 23325 // Common CSS for all blockers 23326 var blockerCSS = { 23327 'bottom': '0px', 23328 'display': 'block', 23329 'position': 'absolute', 23330 'width': this.s.barWidth + 1 + 'px' 23331 }; 23332 this.dom = { 23333 leftBottomBlocker: $$1('<div>') 23334 .css(blockerCSS) 23335 .css('left', 0) 23336 .addClass(this.classes.leftBottomBlocker), 23337 leftTopBlocker: $$1('<div>') 23338 .css(blockerCSS) 23339 .css({ 23340 left: 0, 23341 top: 0 23342 }) 23343 .addClass(this.classes.leftTopBlocker), 23344 rightBottomBlocker: $$1('<div>') 23345 .css(blockerCSS) 23346 .css('right', 0) 23347 .addClass(this.classes.rightBottomBlocker), 23348 rightTopBlocker: $$1('<div>') 23349 .css(blockerCSS) 23350 .css({ 23351 right: 0, 23352 top: 0 23353 }) 23354 .addClass(this.classes.rightTopBlocker) 23355 }; 23356 if (this.s.dt.settings()[0]._bInitComplete) { 23357 // Fixed Columns Initialisation 23358 this._addStyles(); 23359 this._setKeyTableListener(); 23360 } 23361 else { 23362 table.one('init.dt', function () { 23363 // Fixed Columns Initialisation 23364 _this._addStyles(); 23365 _this._setKeyTableListener(); 23366 }); 23367 } 23368 table.on('column-sizing.dt', function () { return _this._addStyles(); }); 23369 // Make class available through dt object 23370 table.settings()[0]._fixedColumns = this; 23371 return this; 23372 } 23373 /** 23374 * Getter/Setter for the `fixedColumns.left` property 23375 * 23376 * @param newVal Optional. If present this will be the new value for the number of left fixed columns 23377 * @returns The number of left fixed columns 23378 */ 23379 FixedColumns.prototype.left = function (newVal) { 23380 // If the value is to change 23381 if (newVal !== undefined) { 23382 // Set the new values and redraw the columns 23383 this.c.left = newVal; 23384 this._addStyles(); 23385 } 23386 return this.c.left; 23387 }; 23388 /** 23389 * Getter/Setter for the `fixedColumns.left` property 23390 * 23391 * @param newVal Optional. If present this will be the new value for the number of right fixed columns 23392 * @returns The number of right fixed columns 23393 */ 23394 FixedColumns.prototype.right = function (newVal) { 23395 // If the value is to change 23396 if (newVal !== undefined) { 23397 // Set the new values and redraw the columns 23398 this.c.right = newVal; 23399 this._addStyles(); 23400 } 23401 return this.c.right; 23402 }; 23403 /** 23404 * Iterates over the columns, fixing the appropriate ones to the left and right 23405 */ 23406 FixedColumns.prototype._addStyles = function () { 23407 // Set the bar width if vertical scrolling is enabled 23408 if (this.s.dt.settings()[0].oScroll.sY) { 23409 var scroll_1 = $$1(this.s.dt.table().node()).closest('div.dataTables_scrollBody')[0]; 23410 var barWidth = this.s.dt.settings()[0].oBrowser.barWidth; 23411 if (scroll_1.offsetWidth - scroll_1.clientWidth >= barWidth) { 23412 this.s.barWidth = barWidth; 23413 } 23414 else { 23415 this.s.barWidth = 0; 23416 } 23417 this.dom.rightTopBlocker.css('width', this.s.barWidth + 1); 23418 this.dom.leftTopBlocker.css('width', this.s.barWidth + 1); 23419 this.dom.rightBottomBlocker.css('width', this.s.barWidth + 1); 23420 this.dom.leftBottomBlocker.css('width', this.s.barWidth + 1); 23421 } 23422 var parentDiv = null; 23423 // Get the header and it's height 23424 var header = this.s.dt.column(0).header(); 23425 var headerHeight = null; 23426 if (header !== null) { 23427 header = $$1(header); 23428 headerHeight = header.outerHeight() + 1; 23429 parentDiv = $$1(header.closest('div.dataTables_scroll')).css('position', 'relative'); 23430 } 23431 // Get the footer and it's height 23432 var footer = this.s.dt.column(0).footer(); 23433 var footerHeight = null; 23434 if (footer !== null) { 23435 footer = $$1(footer); 23436 footerHeight = footer.outerHeight(); 23437 // Only attempt to retrieve the parentDiv if it has not been retrieved already 23438 if (parentDiv === null) { 23439 parentDiv = $$1(footer.closest('div.dataTables_scroll')).css('position', 'relative'); 23440 } 23441 } 23442 // Get the number of columns in the table - this is used often so better to only make 1 api call 23443 var numCols = this.s.dt.columns().data().toArray().length; 23444 // Tracker for the number of pixels should be left to the left of the table 23445 var distLeft = 0; 23446 // Sometimes the headers have slightly different widths so need to track them individually 23447 var headLeft = 0; 23448 // Get all of the row elements in the table 23449 var rows = $$1(this.s.dt.table().node()).children('tbody').children('tr'); 23450 var invisibles = 0; 23451 // When working from right to left we need to know how many are invisible before a point, 23452 // without including those that are invisible after 23453 var prevInvisible = new Map(); 23454 // Iterate over all of the columns 23455 for (var i = 0; i < numCols; i++) { 23456 var column = this.s.dt.column(i); 23457 // Set the map for the previous column 23458 if (i > 0) { 23459 prevInvisible.set(i - 1, invisibles); 23460 } 23461 if (!column.visible()) { 23462 invisibles++; 23463 continue; 23464 } 23465 // Get the columns header and footer element 23466 var colHeader = $$1(column.header()); 23467 var colFooter = $$1(column.footer()); 23468 // If i is less than the value of left then this column should be fixed left 23469 if (i - invisibles < this.c.left) { 23470 $$1(this.s.dt.table().node()).addClass(this.classes.tableFixedLeft); 23471 parentDiv.addClass(this.classes.tableFixedLeft); 23472 // Add the width of the previous node - only if we are on atleast the second column 23473 if (i - invisibles > 0) { 23474 var prevIdx = i; 23475 // Simply using the number of hidden columns doesn't work here, 23476 // if the first is hidden then this would be thrown off
23477 while (prevIdx + 1 < numCols) { 23478 var prevCol = this.s.dt.column(prevIdx - 1, { page: 'current' }); 23479 if (prevCol.visible()) { 23480 distLeft += $$1(prevCol.nodes()[0]).outerWidth(); 23481 headLeft += prevCol.header() ? 23482 $$1(prevCol.header()).outerWidth() : 23483 prevCol.footer() ? 23484 $$1(prevCol.header()).outerWidth() : 23485 0; 23486 break; 23487 } 23488 prevIdx--; 23489 } 23490 } 23491 // Iterate over all of the rows, fixing the cell to the left 23492 for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) { 23493 var row = rows_1[_i]; 23494 $$1($$1(row).children()[i - invisibles]) 23495 .css(this._getCellCSS(false, distLeft, 'left')) 23496 .addClass(this.classes.fixedLeft); 23497 } 23498 // Add the css for the header and the footer 23499 colHeader 23500 .css(this._getCellCSS(true, headLeft, 'left')) 23501 .addClass(this.classes.fixedLeft); 23502 colFooter 23503 .css(this._getCellCSS(true, headLeft, 'left')) 23504 .addClass(this.classes.fixedLeft); 23505 } 23506 else { 23507 // Iteriate through all of the rows, making sure they aren't currently trying to fix left 23508 for (var _a = 0, rows_2 = rows; _a < rows_2.length; _a++) { 23509 var row = rows_2[_a]; 23510 var cell = $$1($$1(row).children()[i - invisibles]); 23511 // If the cell is trying to fix to the left, remove the class and the css 23512 if (cell.hasClass(this.classes.fixedLeft)) { 23513 cell 23514 .css(this._clearCellCSS('left')) 23515 .removeClass(this.classes.fixedLeft); 23516 } 23517 } 23518 // Make sure the header for this column isn't fixed left 23519 if (colHeader.hasClass(this.classes.fixedLeft)) { 23520 colHeader 23521 .css(this._clearCellCSS('left')) 23522 .removeClass(this.classes.fixedLeft); 23523 } 23524 // Make sure the footer for this column isn't fixed left 23525 if (colFooter.hasClass(this.classes.fixedLeft)) { 23526 colFooter 23527 .css(this._clearCellCSS('left')) 23528 .removeClass(this.classes.fixedLeft); 23529 } 23530 } 23531 } 23532 var distRight = 0; 23533 var headRight = 0; 23534 // Counter for the number of invisible columns so far 23535 var rightInvisibles = 0; 23536 for (var i = numCols - 1; i >= 0; i--) { 23537 var column = this.s.dt.column(i); 23538 // If a column is invisible just skip it 23539 if (!column.visible()) { 23540 rightInvisibles++; 23541 continue; 23542 } 23543 // Get the columns header and footer element 23544 var colHeader = $$1(column.header()); 23545 var colFooter = $$1(column.footer()); 23546 // Get the number of visible columns that came before this one 23547 var prev = prevInvisible.get(i); 23548 if (prev === undefined) { 23549 // If it wasn't set then it was the last column so just use the final value 23550 prev = invisibles; 23551 } 23552 if (i + rightInvisibles >= numCols - this.c.right) { 23553 $$1(this.s.dt.table().node()).addClass(this.classes.tableFixedRight); 23554 parentDiv.addClass(this.classes.tableFixedRight); 23555 // Add the widht of the previous node, only if we are on atleast the second column 23556 if (i + 1 + rightInvisibles < numCols) { 23557 var prevIdx = i; 23558 // Simply using the number of hidden columns doesn't work here, 23559 // if the first is hidden then this would be thrown off
23560 while (prevIdx + 1 < numCols) { 23561 var prevCol = this.s.dt.column(prevIdx + 1, { page: 'current' }); 23562 if (prevCol.visible()) { 23563 distRight += $$1(prevCol.nodes()[0]).outerWidth(); 23564 headRight += prevCol.header() ? 23565 $$1(prevCol.header()).outerWidth() : 23566 prevCol.footer() ? 23567 $$1(prevCol.header()).outerWidth() : 23568 0; 23569 break; 23570 } 23571 prevIdx++; 23572 } 23573 } 23574 // Iterate over all of the rows, fixing the cell to the right 23575 for (var _b = 0, rows_3 = rows; _b < rows_3.length; _b++) { 23576 var row = rows_3[_b]; 23577 $$1($$1(row).children()[i - prev]) 23578 .css(this._getCellCSS(false, distRight, 'right')) 23579 .addClass(this.classes.fixedRight); 23580 } 23581 // Add the css for the header and the footer 23582 colHeader 23583 .css(this._getCellCSS(true, headRight, 'right')) 23584 .addClass(this.classes.fixedRight); 23585 colFooter 23586 .css(this._getCellCSS(true, headRight, 'right')) 23587 .addClass(this.classes.fixedRight); 23588 } 23589 else { 23590 // Iteriate through all of the rows, making sure they aren't currently trying to fix right 23591 for (var _c = 0, rows_4 = rows; _c < rows_4.length; _c++) { 23592 var row = rows_4[_c]; 23593 var cell = $$1($$1(row).children()[i - prev]); 23594 // If the cell is trying to fix to the right, remove the class and the css 23595 if (cell.hasClass(this.classes.fixedRight)) { 23596 cell 23597 .css(this._clearCellCSS('right')) 23598 .removeClass(this.classes.fixedRight); 23599 } 23600 } 23601 // Make sure the header for this column isn't fixed right 23602 if (colHeader.hasClass(this.classes.fixedRight)) { 23603 colHeader 23604 .css(this._clearCellCSS('right')) 23605 .removeClass(this.classes.fixedRight); 23606 } 23607 // Make sure the footer for this column isn't fixed right 23608 if (colFooter.hasClass(this.classes.fixedRight)) { 23609 colFooter 23610 .css(this._clearCellCSS('right')) 23611 .removeClass(this.classes.fixedRight); 23612 } 23613 } 23614 } 23615 // If there is a header with the index class and reading rtl then add right top blocker 23616 if (header) { 23617 if (!this.s.rtl) { 23618 this.dom.rightTopBlocker.outerHeight(headerHeight); 23619 parentDiv.append(this.dom.rightTopBlocker); 23620 } 23621 else { 23622 this.dom.leftTopBlocker.outerHeight(headerHeight); 23623 parentDiv.append(this.dom.leftTopBlocker); 23624 } 23625 } 23626 // If there is a footer with the index class and reading rtl then add right bottom blocker 23627 if (footer) { 23628 if (!this.s.rtl) { 23629 this.dom.rightBottomBlocker.outerHeight(footerHeight); 23630 parentDiv.append(this.dom.rightBottomBlocker); 23631 } 23632 else { 23633 this.dom.leftBottomBlocker.outerHeight(footerHeight); 23634 parentDiv.append(this.dom.leftBottomBlocker); 23635 } 23636 } 23637 }; 23638 /** 23639 * Gets the correct CSS for the cell, header or footer based on options provided 23640 * 23641 * @param header Whether this cell is a header or a footer 23642 * @param dist The distance that the cell should be moved away from the edge 23643 * @param lr Indicator of fixing to the left or the right
23644 * @returns An object containing the correct css 23645 */ 23646 FixedColumns.prototype._getCellCSS = function (header, dist, lr) { 23647 if (lr === 'left') { 23648 return this.s.rtl 23649 ? { 23650 position: 'sticky', 23651 right: dist + 'px' 23652 } 23653 : { 23654 left: dist + 'px', 23655 position: 'sticky' 23656 }; 23657 } 23658 else { 23659 return this.s.rtl 23660 ? { 23661 left: dist + (header ? this.s.barWidth : 0) + 'px', 23662 position: 'sticky' 23663 } 23664 : { 23665 position: 'sticky', 23666 right: dist + (header ? this.s.barWidth : 0) + 'px' 23667 }; 23668 } 23669 }; 23670 /** 23671 * Gets the css that is required to clear the fixing to a side 23672 * 23673 * @param lr Indicator of fixing to the left or the right 23674 * @returns An object containing the correct css 23675 */ 23676 FixedColumns.prototype._clearCellCSS = function (lr) { 23677 if (lr === 'left') { 23678 return !this.s.rtl ? 23679 { 23680 left: '', 23681 position: '' 23682 } : 23683 { 23684 position: '', 23685 right: '' 23686 }; 23687 } 23688 else { 23689 return !this.s.rtl ? 23690 { 23691 position: '', 23692 right: '' 23693 } : 23694 { 23695 left: '', 23696 position: '' 23697 }; 23698 } 23699 }; 23700 FixedColumns.prototype._setKeyTableListener = function () { 23701 var _this = this; 23702 this.s.dt.on('key-focus', function (e, dt, cell) { 23703 var cellPos = $$1(cell.node()).offset(); 23704 var scroll = $$1($$1(_this.s.dt.table().node()).closest('div.dataTables_scrollBody')); 23705 // If there are fixed columns to the left 23706 if (_this.c.left > 0) { 23707 // Get the rightmost left fixed column header, it's position and it's width 23708 var rightMost = $$1(_this.s.dt.column(_this.c.left - 1).header()); 23709 var rightMostPos = rightMost.offset(); 23710 var rightMostWidth = rightMost.outerWidth(); 23711 // If the current highlighted cell is left of the rightmost cell on the screen 23712 if (cellPos.left < rightMostPos.left + rightMostWidth) { 23713 // Scroll it into view 23714 var currScroll = scroll.scrollLeft(); 23715 scroll.scrollLeft(currScroll - (rightMostPos.left + rightMostWidth - cellPos.left)); 23716 } 23717 } 23718 // If there are fixed columns to the right 23719 if (_this.c.right > 0) { 23720 // Get the number of columns and the width of the cell as doing right side calc 23721 var numCols = _this.s.dt.columns().data().toArray().length; 23722 var cellWidth = $$1(cell.node()).outerWidth(); 23723 // Get the leftmost right fixed column header and it's position 23724 var leftMost = $$1(_this.s.dt.column(numCols - _this.c.right).header()); 23725 var leftMostPos = leftMost.offset(); 23726 // If the current highlighted cell is right of the leftmost cell on the screen 23727 if (cellPos.left + cellWidth > leftMostPos.left) { 23728 // Scroll it into view 23729 var currScroll = scroll.scrollLeft(); 23730 scroll.scrollLeft(currScroll - (leftMostPos.left - (cellPos.left + cellWidth))); 23731 } 23732 } 23733 }); 23734 // Whenever a draw occurs there is potential for the data to have changed and therefore also the column widths 23735 // Therefore it is necessary to recalculate the values for the fixed columns 23736 this.s.dt.on('draw', function () { 23737 _this._addStyles(); 23738 }); 23739 this.s.dt.on('column-reorder', function () { 23740 _this._addStyles(); 23741 }); 23742 this.s.dt.on('column-visibility', function (e, settings, column, state, recalc) {
23743 if (recalc && !settings.bDestroying) { 23744 setTimeout(function () { 23745 _this._addStyles(); 23746 }, 50); 23747 } 23748 }); 23749 }; 23750 FixedColumns.version = '4.2.2'; 23751 FixedColumns.classes = { 23752 fixedLeft: 'dtfc-fixed-left', 23753 fixedRight: 'dtfc-fixed-right', 23754 leftBottomBlocker: 'dtfc-left-bottom-blocker', 23755 leftTopBlocker: 'dtfc-left-top-blocker', 23756 rightBottomBlocker: 'dtfc-right-bottom-blocker', 23757 rightTopBlocker: 'dtfc-right-top-blocker', 23758 tableFixedLeft: 'dtfc-has-left', 23759 tableFixedRight: 'dtfc-has-right' 23760 }; 23761 FixedColumns.defaults = { 23762 i18n: { 23763 button: 'FixedColumns' 23764 }, 23765 left: 1, 23766 right: 0 23767 }; 23768 return FixedColumns; 23769 }()); 23770 23771 /*! FixedColumns 4.2.2 23772 * © SpryMedia Ltd - datatables.net/license 23773 */ 23774 setJQuery($); 23775 $.fn.dataTable.FixedColumns = FixedColumns; 23776 $.fn.DataTable.FixedColumns = FixedColumns; 23777 var apiRegister = DataTable.Api.register; 23778 apiRegister('fixedColumns()', function () { 23779 return this; 23780 }); 23781 apiRegister('fixedColumns().left()', function (newVal) { 23782 var ctx = this.context[0]; 23783 if (newVal !== undefined) { 23784 ctx._fixedColumns.left(newVal); 23785 return this; 23786 } 23787 else { 23788 return ctx._fixedColumns.left(); 23789 } 23790 }); 23791 apiRegister('fixedColumns().right()', function (newVal) { 23792 var ctx = this.context[0]; 23793 if (newVal !== undefined) { 23794 ctx._fixedColumns.right(newVal); 23795 return this; 23796 } 23797 else { 23798 return ctx._fixedColumns.right(); 23799 } 23800 }); 23801 DataTable.ext.buttons.fixedColumns = { 23802 action: function (e, dt, node, config) { 23803 if ($(node).attr('active')) { 23804 $(node).removeAttr('active').removeClass('active'); 23805 dt.fixedColumns().left(0); 23806 dt.fixedColumns().right(0); 23807 } 23808 else { 23809 $(node).attr('active', 'true').addClass('active'); 23810 dt.fixedColumns().left(config.config.left); 23811 dt.fixedColumns().right(config.config.right); 23812 } 23813 }, 23814 config: { 23815 left: 1, 23816 right: 0 23817 }, 23818 init: function (dt, node, config) { 23819 if (dt.settings()[0]._fixedColumns === undefined) { 23820 _init(dt.settings(), config); 23821 } 23822 $(node).attr('active', 'true').addClass('active'); 23823 dt.button(node).text(config.text || dt.i18n('buttons.fixedColumns', dt.settings()[0]._fixedColumns.c.i18n.button)); 23824 }, 23825 text: null 23826 }; 23827 function _init(settings, options) { 23828 if (options === void 0) { options = null; } 23829 var api = new DataTable.Api(settings); 23830 var opts = options 23831 ? options 23832 : api.init().fixedColumns || DataTable.defaults.fixedColumns; 23833 var fixedColumns = new FixedColumns(api, opts); 23834 return fixedColumns; 23835 } 23836 // Attach a listener to the document which listens for DataTables initialisation 23837 // events so we can automatically initialise 23838 $(document).on('plugin-init.dt', function (e, settings) { 23839 if (e.namespace !== 'dt') { 23840 return; 23841 } 23842 if (settings.oInit.fixedColumns || 23843 DataTable.defaults.fixedColumns) { 23844 if (!settings._fixedColumns) { 23845 _init(settings, null); 23846 } 23847 } 23848 }); 23849 23850})(); 23851 23852 23853return DataTable; 23854})); 23855 23856 23857/*! FixedHeader 3.3.2 23858 * © SpryMedia Ltd - datatables.net/license 23859 */ 23860 23861(function( factory ){ 23862 if ( typeof define === 'function' && define.amd ) { 23863 // AMD 23864 define( ['jquery', 'datatables.net'], function ( $ ) { 23865 return factory( $, window, document ); 23866 } ); 23867 } 23868 else if ( typeof exports === 'object' ) { 23869 // CommonJS 23870 var jq = require('jquery'); 23871 var cjsRequires = function (root, $) { 23872 if ( ! $.fn.dataTable ) { 23873 require('datatables.net')(root, $); 23874 } 23875 }; 23876 23877 if (typeof window !== 'undefined') { 23878 module.exports = function (root, $) { 23879 if ( ! root ) { 23880 // CommonJS environments without a window global must pass a 23881 // root. This will give an error otherwise 23882 root = window; 23883 } 23884 23885 if ( ! $ ) { 23886 $ = jq( root ); 23887 } 23888 23889 cjsRequires( root, $ ); 23890 return factory( $, root, root.document ); 23891 }; 23892 } 23893 else { 23894 cjsRequires( window, jq ); 23895 module.exports = factory( jq, window, window.document ); 23896 } 23897 } 23898 else { 23899 // Browser 23900 factory( jQuery, window, document ); 23901 } 23902}(function( $, window, document, undefined ) { 23903'use strict'; 23904var DataTable = $.fn.dataTable; 23905 23906 23907 23908/** 23909 * @summary FixedHeader 23910 * @description Fix a table's header or footer, so it is always visible while 23911 * scrolling 23912 * @version 3.3.2 23913 * @author SpryMedia Ltd (www.sprymedia.co.uk) 23914 * @contact www.sprymedia.co.uk 23915 * @copyright SpryMedia Ltd. 23916 * 23917 * This source file is free software, available under the following license:
23918 * MIT license - http://datatables.net/license/mit 23919 * 23920 * This source file is distributed in the hope that it will be useful, but 23921 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 23922 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 23923 * 23924 * For details please refer to: http://www.datatables.net 23925 */ 23926 23927var _instCounter = 0; 23928 23929var FixedHeader = function ( dt, config ) { 23930 // Sanity check - you just know it will happen 23931 if ( ! (this instanceof FixedHeader) ) { 23932 throw "FixedHeader must be initialised with the 'new' keyword."; 23933 } 23934 23935 // Allow a boolean true for defaults 23936 if ( config === true ) { 23937 config = {}; 23938 } 23939 23940 dt = new DataTable.Api( dt ); 23941 23942 this.c = $.extend( true, {}, FixedHeader.defaults, config ); 23943 23944 this.s = { 23945 dt: dt, 23946 position: { 23947 theadTop: 0, 23948 tbodyTop: 0, 23949 tfootTop: 0, 23950 tfootBottom: 0, 23951 width: 0, 23952 left: 0, 23953 tfootHeight: 0, 23954 theadHeight: 0, 23955 windowHeight: $(window).height(), 23956 visible: true 23957 }, 23958 headerMode: null, 23959 footerMode: null, 23960 autoWidth: dt.settings()[0].oFeatures.bAutoWidth, 23961 namespace: '.dtfc'+(_instCounter++), 23962 scrollLeft: { 23963 header: -1, 23964 footer: -1 23965 }, 23966 enable: true 23967 }; 23968 23969 this.dom = { 23970 floatingHeader: null, 23971 thead: $(dt.table().header()), 23972 tbody: $(dt.table().body()), 23973 tfoot: $(dt.table().footer()), 23974 header: { 23975 host: null, 23976 floating: null, 23977 floatingParent: $('<div class="dtfh-floatingparent">'), 23978 placeholder: null 23979 }, 23980 footer: { 23981 host: null, 23982 floating: null, 23983 floatingParent: $('<div class="dtfh-floatingparent">'), 23984 placeholder: null 23985 } 23986 }; 23987 23988 this.dom.header.host = this.dom.thead.parent(); 23989 this.dom.footer.host = this.dom.tfoot.parent(); 23990 23991 var dtSettings = dt.settings()[0]; 23992 if ( dtSettings._fixedHeader ) { 23993 throw "FixedHeader already initialised on table "+dtSettings.nTable.id; 23994 } 23995 23996 dtSettings._fixedHeader = this; 23997 23998 this._constructor(); 23999}; 24000 24001 24002/* 24003 * Variable: FixedHeader 24004 * Purpose: Prototype for FixedHeader 24005 * Scope: global 24006 */ 24007$.extend( FixedHeader.prototype, { 24008 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 24009 * API methods 24010 */ 24011 24012 /** 24013 * Kill off FH and any events 24014 */ 24015 destroy: function () { 24016 var dom = this.dom; 24017 24018 this.s.dt.off( '.dtfc' ); 24019 $(window).off( this.s.namespace ); 24020 24021 // Remove clones of FC blockers 24022 if (dom.header.rightBlocker) { 24023 dom.header.rightBlocker.remove(); 24024 } 24025 if (dom.header.leftBlocker) { 24026 dom.header.leftBlocker.remove(); 24027 } 24028 if (dom.footer.rightBlocker) { 24029 dom.footer.rightBlocker.remove(); 24030 } 24031 if (dom.footer.leftBlocker) { 24032 dom.footer.leftBlocker.remove(); 24033 } 24034 24035 if ( this.c.header ) { 24036 this._modeChange( 'in-place', 'header', true ); 24037 } 24038 24039 if ( this.c.footer && dom.tfoot.length ) { 24040 this._modeChange( 'in-place', 'footer', true ); 24041 } 24042 }, 24043 24044 /** 24045 * Enable / disable the fixed elements 24046 * 24047 * @param {boolean} enable `true` to enable, `false` to disable 24048 */ 24049 enable: function ( enable, update ) 24050 { 24051 this.s.enable = enable; 24052 24053 if ( update || update === undefined ) { 24054 this._positions(); 24055 this._scroll( true ); 24056 } 24057 }, 24058 24059 /** 24060 * Get enabled status 24061 */ 24062 enabled: function () 24063 { 24064 return this.s.enable; 24065 }, 24066 24067 /** 24068 * Set header offset 24069 * 24070 * @param {int} new value for headerOffset 24071 */ 24072 headerOffset: function ( offset ) 24073 { 24074 if ( offset !== undefined ) { 24075 this.c.headerOffset = offset; 24076 this.update(); 24077 } 24078 24079 return this.c.headerOffset; 24080 }, 24081 24082 /** 24083 * Set footer offset 24084 * 24085 * @param {int} new value for footerOffset 24086 */ 24087 footerOffset: function ( offset ) 24088 { 24089 if ( offset !== undefined ) { 24090 this.c.footerOffset = offset; 24091 this.update(); 24092 } 24093 24094 return this.c.footerOffset; 24095 }, 24096 24097 24098 /** 24099 * Recalculate the position of the fixed elements and force them into place 24100 */ 24101 update: function (force) 24102 { 24103 if (! this.s.enable) { 24104 return; 24105 } 24106 24107 var table = this.s.dt.table().node(); 24108 24109 if ( $(table).is(':visible') ) { 24110 this.enable( true, false ); 24111 } 24112 else { 24113 this.enable( false, false ); 24114 } 24115 24116 // Don't update if header is not in the document atm (due to 24117 // async events) 24118 if ($(table).children('thead').length === 0) { 24119 return; 24120 } 24121 24122 this._positions(); 24123 this._scroll( force !== undefined ? force : true ); 24124 }, 24125 24126 24127 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 24128 * Constructor 24129 */ 24130 24131 /** 24132 * FixedHeader constructor - adding the required event listeners and 24133 * simple initialisation 24134 * 24135 * @private 24136 */ 24137 _constructor: function () 24138 { 24139 var that = this; 24140 var dt = this.s.dt; 24141 24142 $(window) 24143 .on( 'scroll'+this.s.namespace, function () { 24144 that._scroll(); 24145 } ) 24146 .on( 'resize'+this.s.namespace, DataTable.util.throttle( function () { 24147 that.s.position.windowHeight = $(window).height();
24148 that.update(); 24149 }, 50 ) ); 24150 24151 var autoHeader = $('.fh-fixedHeader'); 24152 if ( ! this.c.headerOffset && autoHeader.length ) { 24153 this.c.headerOffset = autoHeader.outerHeight(); 24154 } 24155 24156 var autoFooter = $('.fh-fixedFooter'); 24157 if ( ! this.c.footerOffset && autoFooter.length ) { 24158 this.c.footerOffset = autoFooter.outerHeight(); 24159 } 24160 24161 dt 24162 .on( 'column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc', function (e, ctx) { 24163 that.update(); 24164 } ) 24165 .on( 'draw.dt.dtfc', function (e, ctx) { 24166 // For updates from our own table, don't reclone, but for all others, do 24167 that.update(ctx === dt.settings()[0] ? false : true); 24168 } ); 24169 24170 dt.on( 'destroy.dtfc', function () { 24171 that.destroy(); 24172 } ); 24173 24174 this._positions(); 24175 this._scroll(); 24176 }, 24177 24178 24179 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 24180 * Private methods 24181 */ 24182 24183 /** 24184 * Clone a fixed item to act as a place holder for the original element 24185 * which is moved into a clone of the table element, and moved around the 24186 * document to give the fixed effect. 24187 * 24188 * @param {string} item 'header' or 'footer' 24189 * @param {boolean} force Force the clone to happen, or allow automatic 24190 * decision (reuse existing if available) 24191 * @private 24192 */ 24193 _clone: function ( item, force ) 24194 { 24195 var that = this; 24196 var dt = this.s.dt; 24197 var itemDom = this.dom[ item ]; 24198 var itemElement = item === 'header' ? 24199 this.dom.thead : 24200 this.dom.tfoot; 24201 24202 // If footer and scrolling is enabled then we don't clone 24203 // Instead the table's height is decreased accordingly - see `_scroll()` 24204 if (item === 'footer' && this._scrollEnabled()) { 24205 return; 24206 } 24207 24208 if ( ! force && itemDom.floating ) { 24209 // existing floating element - reuse it 24210 itemDom.floating.removeClass( 'fixedHeader-floating fixedHeader-locked' ); 24211 } 24212 else { 24213 var docScrollLeft = $(document).scrollLeft(); 24214 var docScrollTop = $(document).scrollTop(); 24215 24216 if ( itemDom.floating ) { 24217 if(itemDom.placeholder !== null) { 24218 itemDom.placeholder.remove(); 24219 } 24220 this._unsize( item ); 24221 itemDom.floating.children().detach(); 24222 itemDom.floating.remove(); 24223 } 24224 24225 var tableNode = $(dt.table().node()); 24226 var scrollBody = $(tableNode.parent()); 24227 var scrollEnabled = this._scrollEnabled(); 24228 24229 itemDom.floating = $( dt.table().node().cloneNode( false ) ) 24230 .attr( 'aria-hidden', 'true' ) 24231 .css({ 24232 'table-layout': 'fixed', 24233 top: 0, 24234 left: 0 24235 }) 24236 .removeAttr( 'id' ) 24237 .append( itemElement ); 24238 24239 itemDom.floatingParent 24240 .css({ 24241 width: scrollBody.width(), 24242 overflow: 'hidden', 24243 height: 'fit-content', 24244 position: 'fixed', 24245 left: scrollEnabled ? tableNode.offset().left + scrollBody.scrollLeft() : 0 24246 }) 24247 .css( 24248 item === 'header' ? 24249 { 24250 top: this.c.headerOffset, 24251 bottom: '' 24252 } : 24253 { 24254 top: '', 24255 bottom: this.c.footerOffset 24256 } 24257 ) 24258 .addClass(item === 'footer' ? 'dtfh-floatingparentfoot' : 'dtfh-floatingparenthead') 24259 .append(itemDom.floating) 24260 .appendTo( 'body' ); 24261 24262 this._stickyPosition(itemDom.floating, '-'); 24263 24264 var scrollLeftUpdate = function () { 24265 var scrollLeft = scrollBody.scrollLeft() 24266 that.s.scrollLeft = {footer: scrollLeft, header: scrollLeft}; 24267 itemDom.floatingParent.scrollLeft(that.s.scrollLeft.header); 24268 } 24269 24270 scrollLeftUpdate(); 24271 scrollBody 24272 .off('scroll.dtfh') 24273 .on('scroll.dtfh', scrollLeftUpdate); 24274 24275 // Insert a fake thead/tfoot into the DataTable to stop it jumping around 24276 itemDom.placeholder = itemElement.clone( false ); 24277 itemDom.placeholder 24278 .find( '*[id]' ) 24279 .removeAttr( 'id' ); 24280 24281 itemDom.host.prepend( itemDom.placeholder ); 24282 24283 // Clone widths 24284 this._matchWidths( itemDom.placeholder, itemDom.floating ); 24285 24286 // The above action will remove the table header, potentially causing the table to 24287 // collapse to a smaller size, before it is then re-inserted (append). The result 24288 // can be that the document, if scrolling, can "jump". 24289 $(document) 24290 .scrollTop(docScrollTop)
24291 .scrollLeft(docScrollLeft); 24292 } 24293 }, 24294 24295 /** 24296 * This method sets the sticky position of the header elements to match fixed columns 24297 * @param {JQuery<HTMLElement>} el 24298 * @param {string} sign 24299 */ 24300 _stickyPosition: function(el, sign) { 24301 if (this._scrollEnabled()) { 24302 var that = this 24303 var rtl = $(that.s.dt.table().node()).css('direction') === 'rtl'; 24304 24305 el.find('th').each(function() { 24306 // Find out if fixed header has previously set this column 24307 if ($(this).css('position') === 'sticky') { 24308 var right = $(this).css('right'); 24309 var left = $(this).css('left'); 24310 if (right !== 'auto' && !rtl) { 24311 // New position either adds or dismisses the barWidth 24312 var potential = +right.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth; 24313 $(this).css('right', potential > 0 ? potential : 0); 24314 } 24315 else if(left !== 'auto' && rtl) { 24316 var potential = +left.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth; 24317 $(this).css('left', potential > 0 ? potential : 0); 24318 } 24319 } 24320 }); 24321 } 24322 }, 24323 24324 /** 24325 * Copy widths from the cells in one element to another. This is required 24326 * for the footer as the footer in the main table takes its sizes from the 24327 * header columns. That isn't present in the footer so to have it still 24328 * align correctly, the sizes need to be copied over. It is also required 24329 * for the header when auto width is not enabled 24330 * 24331 * @param {jQuery} from Copy widths from 24332 * @param {jQuery} to Copy widths to 24333 * @private 24334 */ 24335 _matchWidths: function ( from, to ) { 24336 var get = function ( name ) { 24337 return $(name, from) 24338 .map( function () { 24339 return $(this).css('width').replace(/[^\d\.]/g, '') * 1; 24340 } ).toArray(); 24341 }; 24342 24343 var set = function ( name, toWidths ) { 24344 $(name, to).each( function ( i ) { 24345 $(this).css( { 24346 width: toWidths[i], 24347 minWidth: toWidths[i] 24348 } ); 24349 } ); 24350 }; 24351 24352 var thWidths = get( 'th' ); 24353 var tdWidths = get( 'td' ); 24354 24355 set( 'th', thWidths ); 24356 set( 'td', tdWidths ); 24357 }, 24358 24359 /** 24360 * Remove assigned widths from the cells in an element. This is required 24361 * when inserting the footer back into the main table so the size is defined 24362 * by the header columns and also when auto width is disabled in the 24363 * DataTable. 24364 * 24365 * @param {string} item The `header` or `footer` 24366 * @private 24367 */ 24368 _unsize: function ( item ) { 24369 var el = this.dom[ item ].floating; 24370 24371 if ( el && (item === 'footer' || (item === 'header' && ! this.s.autoWidth)) ) { 24372 $('th, td', el).css( { 24373 width: '', 24374 minWidth: '' 24375 } ); 24376 } 24377 else if ( el && item === 'header' ) { 24378 $('th, td', el).css( 'min-width', '' ); 24379 } 24380 }, 24381 24382 /** 24383 * Reposition the floating elements to take account of horizontal page 24384 * scroll 24385 * 24386 * @param {string} item The `header` or `footer` 24387 * @param {int} scrollLeft Document scrollLeft 24388 * @private 24389 */ 24390 _horizontal: function ( item, scrollLeft ) 24391 { 24392 var itemDom = this.dom[ item ]; 24393 var position = this.s.position; 24394 var lastScrollLeft = this.s.scrollLeft; 24395 24396 if ( itemDom.floating && lastScrollLeft[ item ] !== scrollLeft ) { 24397 // If scrolling is enabled we need to match the floating header to the body 24398 if (this._scrollEnabled()) { 24399 var newScrollLeft = $($(this.s.dt.table().node()).parent()).scrollLeft() 24400 itemDom.floating.scrollLeft(newScrollLeft); 24401 itemDom.floatingParent.scrollLeft(newScrollLeft); 24402 } 24403 24404 lastScrollLeft[ item ] = scrollLeft; 24405 } 24406 }, 24407 24408 /** 24409 * Change from one display mode to another. Each fixed item can be in one 24410 * of: 24411 * 24412 * * `in-place` - In the main DataTable 24413 * * `in` - Floating over the DataTable 24414 * * `below` - (Header only) Fixed to the bottom of the table body 24415 * * `above` - (Footer only) Fixed to the top of the table body 24416 * 24417 * @param {string} mode Mode that the item should be shown in 24418 * @param {string} item 'header' or 'footer' 24419 * @param {boolean} forceChange Force a redraw of the mode, even if already 24420 * in that mode. 24421 * @private 24422 */ 24423 _modeChange: function ( mode, item, forceChange ) 24424 { 24425 var dt = this.s.dt; 24426 var itemDom = this.dom[ item ]; 24427 var position = this.s.position; 24428 24429 // Just determine if scroll is enabled once 24430 var scrollEnabled = this._scrollEnabled(); 24431 24432 // If footer and scrolling is enabled then we don't clone 24433 // Instead the table's height is decreased accordingly - see `_scroll()` 24434 if (item === 'footer' && scrollEnabled) { 24435 return; 24436 } 24437 24438 // It isn't trivial to add a !important css attribute... 24439 var importantWidth = function (w) { 24440 itemDom.floating.attr('style', function(i,s) { 24441 return (s || '') + 'width: '+w+'px !important;'; 24442 }); 24443 24444 // If not scrolling also have to update the floatingParent 24445 if (!scrollEnabled) { 24446 itemDom.floatingParent.attr('style', function(i,s) { 24447 return (s || '') + 'width: '+w+'px !important;'; 24448 }); 24449 } 24450 }; 24451 24452 // Record focus. Browser's will cause input elements to loose focus if 24453 // they are inserted else where in the doc 24454 var tablePart = this.dom[ item==='footer' ? 'tfoot' : 'thead' ]; 24455 var focus = $.contains( tablePart[0], document.activeElement ) ? 24456 document.activeElement : 24457 null; 24458 var scrollBody = $($(this.s.dt.table().node()).parent()); 24459 24460 if ( mode === 'in-place' ) { 24461 // Insert the header back into the table's real header 24462 if ( itemDom.placeholder ) { 24463 itemDom.placeholder.remove(); 24464 itemDom.placeholder = null; 24465 } 24466 24467 this._unsize( item ); 24468 24469 if ( item === 'header' ) { 24470 itemDom.host.prepend( tablePart ); 24471 } 24472 else { 24473 itemDom.host.append( tablePart ); 24474 } 24475 24476 if ( itemDom.floating ) { 24477 itemDom.floating.remove(); 24478 itemDom.floating = null; 24479 this._stickyPosition(itemDom.host, '+'); 24480 } 24481 24482 if ( itemDom.floatingParent ) { 24483 itemDom.floatingParent.remove(); 24484 } 24485 24486 $($(itemDom.host.parent()).parent()).scrollLeft(scrollBody.scrollLeft()) 24487 } 24488 else if ( mode === 'in' ) { 24489 // Remove the header from the read header and insert into a fixed 24490 // positioned floating table clone 24491 this._clone( item, forceChange ); 24492 24493 // Get useful position values 24494 var scrollOffset = scrollBody.offset(); 24495 var windowTop = $(document).scrollTop(); 24496 var windowHeight = $(window).height(); 24497 var windowBottom = windowTop + windowHeight; 24498 var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop; 24499 var bodyBottom = scrollEnabled ? scrollOffset.top + scrollBody.outerHeight() : position.tfootTop 24500 24501 // Calculate the amount that the footer or header needs to be shuffled 24502 var shuffle = item === 'footer' ? 24503 // footer and top of body isn't on screen 24504 bodyTop > windowBottom ? 24505 // Yes - push the footer below 24506 position.tfootHeight : 24507 // No - bottom set to the gap between the top of the body and the bottom of the window 24508 bodyTop + position.tfootHeight - windowBottom : 24509 // Otherwise must be a header so get the difference from the bottom of the 24510 // desired floating header and the bottom of the table body 24511 windowTop + this.c.headerOffset + position.theadHeight - bodyBottom 24512 24513 // Set the top or bottom based off of the offset and the shuffle value 24514 var prop = item === 'header' ? 'top' : 'bottom'; 24515 var val = this.c[item+'Offset'] - (shuffle > 0 ? shuffle : 0); 24516 24517 itemDom.floating.addClass( 'fixedHeader-floating' ); 24518 itemDom.floatingParent 24519 .css(prop, val) 24520 .css( { 24521 'left': position.left, 24522 'height': item === 'header' ? position.theadHeight : position.tfootHeight, 24523 'z-index': 2 24524 }) 24525 .append(itemDom.floating); 24526 24527 importantWidth(position.width); 24528 24529 if ( item === 'footer' ) {
24530 itemDom.floating.css( 'top', '' ); 24531 } 24532 } 24533 else if ( mode === 'below' ) { // only used for the header 24534 // Fix the position of the floating header at base of the table body 24535 this._clone( item, forceChange ); 24536 24537 itemDom.floating.addClass( 'fixedHeader-locked' ); 24538 itemDom.floatingParent.css({ 24539 position: 'absolute', 24540 top: position.tfootTop - position.theadHeight, 24541 left: position.left+'px' 24542 }); 24543 24544 importantWidth(position.width); 24545 } 24546 else if ( mode === 'above' ) { // only used for the footer 24547 // Fix the position of the floating footer at top of the table body 24548 this._clone( item, forceChange ); 24549 24550 itemDom.floating.addClass( 'fixedHeader-locked' ); 24551 itemDom.floatingParent.css({ 24552 position: 'absolute', 24553 top: position.tbodyTop, 24554 left: position.left+'px' 24555 }); 24556 24557 importantWidth(position.width); 24558 } 24559 24560 // Restore focus if it was lost 24561 if ( focus && focus !== document.activeElement ) { 24562 setTimeout( function () { 24563 focus.focus(); 24564 }, 10 ); 24565 } 24566 24567 this.s.scrollLeft.header = -1; 24568 this.s.scrollLeft.footer = -1; 24569 this.s[item+'Mode'] = mode; 24570 }, 24571 24572 /** 24573 * Cache the positional information that is required for the mode 24574 * calculations that FixedHeader performs. 24575 * 24576 * @private 24577 */ 24578 _positions: function () 24579 { 24580 var dt = this.s.dt; 24581 var table = dt.table(); 24582 var position = this.s.position; 24583 var dom = this.dom; 24584 var tableNode = $(table.node()); 24585 var scrollEnabled = this._scrollEnabled(); 24586 24587 // Need to use the header and footer that are in the main table, 24588 // regardless of if they are clones, since they hold the positions we 24589 // want to measure from 24590 var thead = $(dt.table().header()); 24591 var tfoot = $(dt.table().footer()); 24592 var tbody = dom.tbody; 24593 var scrollBody = tableNode.parent(); 24594 24595 position.visible = tableNode.is(':visible'); 24596 position.width = tableNode.outerWidth(); 24597 position.left = tableNode.offset().left; 24598 position.theadTop = thead.offset().top; 24599 position.tbodyTop = scrollEnabled ? scrollBody.offset().top : tbody.offset().top; 24600 position.tbodyHeight = scrollEnabled ? scrollBody.outerHeight() : tbody.outerHeight(); 24601 position.theadHeight = thead.outerHeight(); 24602 position.theadBottom = position.theadTop + position.theadHeight; 24603 24604 if ( tfoot.length ) { 24605 position.tfootTop = position.tbodyTop + position.tbodyHeight; //tfoot.offset().top; 24606 position.tfootBottom = position.tfootTop + tfoot.outerHeight(); 24607 position.tfootHeight = tfoot.outerHeight(); 24608 } 24609 else { 24610 position.tfootTop = position.tbodyTop + tbody.outerHeight(); 24611 position.tfootBottom = position.tfootTop; 24612 position.tfootHeight = position.tfootTop; 24613 } 24614 }, 24615 24616 24617 /** 24618 * Mode calculation - determine what mode the fixed items should be placed 24619 * into. 24620 * 24621 * @param {boolean} forceChange Force a redraw of the mode, even if already 24622 * in that mode. 24623 * @private 24624 */ 24625 _scroll: function ( forceChange ) 24626 { 24627 if (this.s.dt.settings()[0].bDestroying) { 24628 return; 24629 } 24630 24631 // ScrollBody details 24632 var scrollEnabled = this._scrollEnabled(); 24633 var scrollBody = $(this.s.dt.table().node()).parent(); 24634 var scrollOffset = scrollBody.offset(); 24635 var scrollHeight = scrollBody.outerHeight(); 24636 24637 // Window details 24638 var windowLeft = $(document).scrollLeft(); 24639 var windowTop = $(document).scrollTop(); 24640 var windowHeight = $(window).height(); 24641 var windowBottom = windowHeight + windowTop 24642 24643 24644 var position = this.s.position; 24645 var headerMode, footerMode; 24646 24647 // Body Details 24648 var bodyTop = (scrollEnabled ? scrollOffset.top : position.tbodyTop); 24649 var bodyLeft = (scrollEnabled ? scrollOffset.left : position.left); 24650 var bodyBottom = (scrollEnabled ? scrollOffset.top + scrollHeight : position.tfootTop); 24651 var bodyWidth = (scrollEnabled ? scrollBody.outerWidth() : position.tbodyWidth); 24652 24653 var windowBottom = windowTop + windowHeight; 24654 24655 if ( this.c.header ) { 24656 if ( ! this.s.enable ) { 24657 headerMode = 'in-place'; 24658 } 24659 // The header is in it's normal place if the body top is lower than 24660 // the scroll of the window plus the headerOffset and the height of the header 24661 else if ( ! position.visible || windowTop + this.c.headerOffset + position.theadHeight <= bodyTop) { 24662 headerMode = 'in-place'; 24663 } 24664 // The header should be floated if 24665 else if ( 24666 // The scrolling plus the header offset plus the height of the header is lower than the top of the body 24667 windowTop + this.c.headerOffset + position.theadHeight > bodyTop && 24668 // And the scrolling at the top plus the header offset is above the bottom of the body 24669 windowTop + this.c.headerOffset + position.theadHeight < bodyBottom 24670 ) { 24671 headerMode = 'in'; 24672 var scrollBody = $($(this.s.dt.table().node()).parent()); 24673 24674 // Further to the above, If the scrolling plus the header offset plus the header height is lower 24675 // than the bottom of the table a shuffle is required so have to force the calculation 24676 if(windowTop + this.c.headerOffset + position.theadHeight > bodyBottom || this.dom.header.floatingParent === undefined){ 24677 forceChange = true; 24678 } 24679 else { 24680 this.dom.header.floatingParent 24681 .css({ 24682 'top': this.c.headerOffset, 24683 'position': 'fixed' 24684 }) 24685 .append(this.dom.header.floating); 24686 } 24687 } 24688 // Anything else and the view is below the table 24689 else { 24690 headerMode = 'below'; 24691 } 24692 24693 if ( forceChange || headerMode !== this.s.headerMode ) { 24694 this._modeChange( headerMode, 'header', forceChange ); 24695 } 24696 24697 this._horizontal( 'header', windowLeft ); 24698 } 24699 24700 var header = { 24701 offset: {top: 0, left: 0}, 24702 height: 0 24703 } 24704 var footer = { 24705 offset: {top: 0, left: 0}, 24706 height: 0 24707 } 24708 24709 if ( this.c.footer && this.dom.tfoot.length ) { 24710 if ( ! this.s.enable ) { 24711 footerMode = 'in-place'; 24712 } 24713 else if ( ! position.visible || position.tfootBottom + this.c.footerOffset <= windowBottom ) { 24714 footerMode = 'in-place'; 24715 } 24716 else if ( 24717 bodyBottom + position.tfootHeight + this.c.footerOffset > windowBottom && 24718 bodyTop + this.c.footerOffset < windowBottom 24719 ) { 24720 footerMode = 'in'; 24721 forceChange = true; 24722 } 24723 else { 24724 footerMode = 'above'; 24725 } 24726 24727 if ( forceChange || footerMode !== this.s.footerMode ) { 24728 this._modeChange( footerMode, 'footer', forceChange ); 24729 } 24730 24731 this._horizontal( 'footer', windowLeft ); 24732 24733 var getOffsetHeight = function (el) { 24734 return { 24735 offset: el.offset(), 24736 height: el.outerHeight() 24737 }; 24738 }; 24739 24740 header = this.dom.header.floating ? getOffsetHeight(this.dom.header.floating) : getOffsetHeight(this.dom.thead); 24741 footer = this.dom.footer.floating ? getOffsetHeight(this.dom.footer.floating) : getOffsetHeight(this.dom.tfoot); 24742 24743 // If scrolling is enabled and the footer is off the screen 24744 if (scrollEnabled && footer.offset.top > windowTop){// && footer.offset.top >= windowBottom) { 24745 // Calculate the gap between the top of the scrollBody and the top of the window 24746 var overlap = windowTop - scrollOffset.top; 24747 // The new height is the bottom of the window 24748 var newHeight = windowBottom + 24749 // If the gap between the top of the scrollbody and the window is more than 24750 // the height of the header then the top of the table is still visible so add that gap 24751 // Doing this has effectively calculated the height from the top of the table to the bottom of the current page 24752 (overlap > -header.height ? overlap : 0) - 24753 // Take from that 24754 ( 24755 // The top of the header plus 24756 header.offset.top + 24757 // The header height if the standard header is present 24758 (overlap < -header.height ? header.height : 0) + 24759 // And the height of the footer 24760 footer.height 24761 ) 24762 24763 // Don't want a negative height 24764 if (newHeight < 0) { 24765 newHeight = 0; 24766 } 24767 24768 // At the end of the above calculation the space between the header (top of the page if floating) 24769 // and the point just above the footer should be the new value for the height of the table. 24770 scrollBody.outerHeight(newHeight); 24771 24772 // Need some rounding here as sometimes very small decimal places are encountered 24773 // If the actual height is bigger or equal to the height we just applied then the footer is "Floating" 24774 if(Math.round(scrollBody.outerHeight()) >= Math.round(newHeight)) { 24775 $(this.dom.tfoot.parent()).addClass("fixedHeader-floating"); 24776 } 24777 // Otherwise max-width has kicked in so it is not floating 24778 else { 24779 $(this.dom.tfoot.parent()).removeClass("fixedHeader-floating"); 24780 } 24781 } 24782 } 24783 24784 if(this.dom.header.floating){ 24785 this.dom.header.floatingParent.css('left', bodyLeft-windowLeft); 24786 } 24787 if(this.dom.footer.floating){ 24788 this.dom.footer.floatingParent.css('left', bodyLeft-windowLeft); 24789 } 24790 24791 // If fixed columns is being used on this table then the blockers need to be copied across 24792 // Cloning these is cleaner than creating as our own as it will keep consistency with fixedColumns automatically 24793 // ASSUMING that the class remains the same 24794 if (this.s.dt.settings()[0]._fixedColumns !== undefined) { 24795 var adjustBlocker = function (side, end, el) { 24796 if (el === undefined) { 24797 var blocker = $('div.dtfc-'+side+'-'+end+'-blocker'); 24798 24799 el = blocker.length === 0 ? 24800 null : 24801 blocker.clone().css('z-index', 1); 24802 } 24803 24804 if(el !== null) { 24805 if (headerMode === 'in' || headerMode === 'below') { 24806 el 24807 .appendTo('body') 24808 .css({ 24809 top: end === 'top' ? header.offset.top : footer.offset.top, 24810 left: side === 'right' ? bodyLeft + bodyWidth - el.width() : bodyLeft 24811 });
24812 } 24813 else { 24814 el.detach(); 24815 } 24816 } 24817 24818 return el; 24819 } 24820 24821 // Adjust all blockers 24822 this.dom.header.rightBlocker = adjustBlocker('right', 'top', this.dom.header.rightBlocker); 24823 this.dom.header.leftBlocker = adjustBlocker('left', 'top', this.dom.header.leftBlocker); 24824 this.dom.footer.rightBlocker = adjustBlocker('right', 'bottom', this.dom.footer.rightBlocker); 24825 this.dom.footer.leftBlocker = adjustBlocker('left', 'bottom', this.dom.footer.leftBlocker); 24826 } 24827 }, 24828 24829 /** 24830 * Function to check if scrolling is enabled on the table or not 24831 * @returns Boolean value indicating if scrolling on the table is enabled or not 24832 */ 24833 _scrollEnabled: function() { 24834 var oScroll = this.s.dt.settings()[0].oScroll; 24835 if(oScroll.sY !== "" || oScroll.sX !== "") { 24836 return true; 24837 } 24838 return false 24839 } 24840} ); 24841 24842 24843/** 24844 * Version 24845 * @type {String} 24846 * @static 24847 */ 24848FixedHeader.version = "3.3.2"; 24849 24850/** 24851 * Defaults 24852 * @type {Object} 24853 * @static 24854 */ 24855FixedHeader.defaults = { 24856 header: true, 24857 footer: false, 24858 headerOffset: 0, 24859 footerOffset: 0 24860}; 24861 24862 24863/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 24864 * DataTables interfaces 24865 */ 24866 24867// Attach for constructor access 24868$.fn.dataTable.FixedHeader = FixedHeader; 24869$.fn.DataTable.FixedHeader = FixedHeader; 24870 24871 24872// DataTables creation - check if the FixedHeader option has been defined on the 24873// table and if so, initialise 24874$(document).on( 'init.dt.dtfh', function (e, settings, json) { 24875 if ( e.namespace !== 'dt' ) { 24876 return; 24877 } 24878 24879 var init = settings.oInit.fixedHeader; 24880 var defaults = DataTable.defaults.fixedHeader; 24881 24882 if ( (init || defaults) && ! settings._fixedHeader ) { 24883 var opts = $.extend( {}, defaults, init ); 24884 24885 if ( init !== false ) { 24886 new FixedHeader( settings, opts ); 24887 } 24888 } 24889} ); 24890 24891// DataTables API methods 24892DataTable.Api.register( 'fixedHeader()', function () {} ); 24893 24894DataTable.Api.register( 'fixedHeader.adjust()', function () { 24895 return this.iterator( 'table', function ( ctx ) { 24896 var fh = ctx._fixedHeader; 24897 24898 if ( fh ) { 24899 fh.update(); 24900 } 24901 } ); 24902} ); 24903 24904DataTable.Api.register( 'fixedHeader.enable()', function ( flag ) { 24905 return this.iterator( 'table', function ( ctx ) { 24906 var fh = ctx._fixedHeader; 24907 24908 flag = ( flag !== undefined ? flag : true ); 24909 if ( fh && flag !== fh.enabled() ) { 24910 fh.enable( flag ); 24911 } 24912 } ); 24913} ); 24914 24915DataTable.Api.register( 'fixedHeader.enabled()', function () { 24916 if ( this.context.length ) { 24917 var fh = this.context[0]._fixedHeader; 24918 24919 if ( fh ) { 24920 return fh.enabled(); 24921 } 24922 } 24923 24924 return false; 24925} ); 24926
24927DataTable.Api.register( 'fixedHeader.disable()', function ( ) { 24928 return this.iterator( 'table', function ( ctx ) { 24929 var fh = ctx._fixedHeader; 24930 24931 if ( fh && fh.enabled() ) { 24932 fh.enable( false ); 24933 } 24934 } ); 24935} ); 24936 24937$.each( ['header', 'footer'], function ( i, el ) { 24938 DataTable.Api.register( 'fixedHeader.'+el+'Offset()', function ( offset ) { 24939 var ctx = this.context; 24940 24941 if ( offset === undefined ) { 24942 return ctx.length && ctx[0]._fixedHeader ? 24943 ctx[0]._fixedHeader[el +'Offset']() : 24944 undefined; 24945 } 24946 24947 return this.iterator( 'table', function ( ctx ) { 24948 var fh = ctx._fixedHeader; 24949 24950 if ( fh ) { 24951 fh[ el +'Offset' ]( offset ); 24952 } 24953 } ); 24954 } ); 24955} ); 24956 24957 24958return DataTable; 24959})); 24960 24961 24962/*! KeyTable 2.9.0 24963 * © SpryMedia Ltd - datatables.net/license 24964 */ 24965 24966(function( factory ){ 24967 if ( typeof define === 'function' && define.amd ) { 24968 // AMD 24969 define( ['jquery', 'datatables.net'], function ( $ ) { 24970 return factory( $, window, document ); 24971 } ); 24972 } 24973 else if ( typeof exports === 'object' ) { 24974 // CommonJS 24975 var jq = require('jquery'); 24976 var cjsRequires = function (root, $) { 24977 if ( ! $.fn.dataTable ) { 24978 require('datatables.net')(root, $); 24979 } 24980 }; 24981 24982 if (typeof window === 'undefined') { 24983 module.exports = function (root, $) { 24984 if ( ! root ) { 24985 // CommonJS environments without a window global must pass a 24986 // root. This will give an error otherwise 24987 root = window; 24988 } 24989 24990 if ( ! $ ) { 24991 $ = jq( root ); 24992 } 24993 24994 cjsRequires( root, $ ); 24995 return factory( $, root, root.document ); 24996 }; 24997 } 24998 else { 24999 cjsRequires( window, jq ); 25000 module.exports = factory( jq, window, window.document ); 25001 } 25002 } 25003 else { 25004 // Browser 25005 factory( jQuery, window, document ); 25006 } 25007}(function( $, window, document, undefined ) { 25008'use strict'; 25009var DataTable = $.fn.dataTable; 25010 25011 25012 25013/** 25014 * @summary KeyTable 25015 * @description Spreadsheet like keyboard navigation for DataTables 25016 * @version 2.9.0 25017 * @file dataTables.keyTable.js 25018 * @author SpryMedia Ltd 25019 * @contact datatables.net 25020 * @copyright Copyright SpryMedia Ltd. 25021 * 25022 * This source file is free software, available under the following license: 25023 * MIT license - http://datatables.net/license/mit 25024 * 25025 * This source file is distributed in the hope that it will be useful, but 25026 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 25027 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 25028 * 25029 * For details please refer to: http://www.datatables.net 25030 */ 25031 25032var namespaceCounter = 0; 25033var editorNamespaceCounter = 0; 25034 25035 25036var KeyTable = function ( dt, opts ) { 25037 // Sanity check that we are using DataTables 1.10 or newer 25038 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { 25039 throw 'KeyTable requires DataTables 1.10.8 or newer'; 25040 } 25041 25042 // User and defaults configuration object 25043 this.c = $.extend( true, {}, 25044 DataTable.defaults.keyTable, 25045 KeyTable.defaults, 25046 opts 25047 ); 25048 25049 // Internal settings 25050 this.s = { 25051 /** @type {DataTable.Api} DataTables' API instance */ 25052 dt: new DataTable.Api( dt ), 25053 25054 enable: true, 25055 25056 /** @type {bool} Flag for if a draw is triggered by focus */ 25057 focusDraw: false, 25058 25059 /** @type {bool} Flag to indicate when waiting for a draw to happen. 25060 * Will ignore key presses at this point 25061 */ 25062 waitingForDraw: false, 25063 25064 /** @type {object} Information about the last cell that was focused */ 25065 lastFocus: null, 25066 25067 /** @type {string} Unique namespace per instance */ 25068 namespace: '.keyTable-'+(namespaceCounter++), 25069 25070 /** @type {Node} Input element for tabbing into the table */ 25071 tabInput: null 25072 }; 25073 25074 // DOM items 25075 this.dom = { 25076 25077 }; 25078 25079 // Check if row reorder has already been initialised on this table 25080 var settings = this.s.dt.settings()[0]; 25081 var exisiting = settings.keytable; 25082 if ( exisiting ) { 25083 return exisiting; 25084 } 25085 25086 settings.keytable = this; 25087 this._constructor(); 25088}; 25089 25090 25091$.extend( KeyTable.prototype, { 25092 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 25093 * API methods for DataTables API interface 25094 */ 25095 25096 /** 25097 * Blur the table's cell focus 25098 */ 25099 blur: function () 25100 { 25101 this._blur(); 25102 }, 25103 25104 /** 25105 * Enable cell focus for the table 25106 * 25107 * @param {string} state Can be `true`, `false` or `-string navigation-only` 25108 */ 25109 enable: function ( state ) 25110 { 25111 this.s.enable = state; 25112 }, 25113 25114 /** 25115 * Get enable status 25116 */ 25117 enabled: function () { 25118 return this.s.enable; 25119 }, 25120 25121 /** 25122 * Focus on a cell 25123 * @param {integer} row Row index 25124 * @param {integer} column Column index 25125 */ 25126 focus: function ( row, column ) 25127 { 25128 this._focus( this.s.dt.cell( row, column ) ); 25129 }, 25130 25131 /** 25132 * Is the cell focused 25133 * @param {object} cell Cell index to check 25134 * @returns {boolean} true if focused, false otherwise 25135 */ 25136 focused: function ( cell ) 25137 { 25138 var lastFocus = this.s.lastFocus; 25139 25140 if ( ! lastFocus ) { 25141 return false; 25142 } 25143 25144 var lastIdx = this.s.lastFocus.cell.index(); 25145 return cell.row === lastIdx.row && cell.column === lastIdx.column; 25146 }, 25147 25148 25149 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 25150 * Constructor 25151 */ 25152 25153 /** 25154 * Initialise the KeyTable instance 25155 * 25156 * @private 25157 */ 25158 _constructor: function () 25159 { 25160 this._tabInput(); 25161 25162 var that = this; 25163 var dt = this.s.dt; 25164 var table = $( dt.table().node() ); 25165 var namespace = this.s.namespace; 25166 var editorBlock = false; 25167 25168 // Need to be able to calculate the cell positions relative to the table 25169 if ( table.css('position') === 'static' ) { 25170 table.css( 'position', 'relative' ); 25171 } 25172 25173 // Click to focus 25174 $( dt.table().body() ).on( 'click'+namespace, 'th, td', function (e) { 25175 if ( that.s.enable === false ) { 25176 return; 25177 } 25178 25179 var cell = dt.cell( this ); 25180 25181 if ( ! cell.any() ) { 25182 return; 25183 } 25184 25185 that._focus( cell, null, false, e ); 25186 } ); 25187 25188 // Key events 25189 $( document ).on( 'keydown'+namespace, function (e) { 25190 if ( ! editorBlock ) { 25191 that._key( e ); 25192 } 25193 } ); 25194 25195 // Click blur 25196 if ( this.c.blurable ) { 25197 $( document ).on( 'mousedown'+namespace, function ( e ) { 25198 // Click on the search input will blur focus 25199 if ( $(e.target).parents( '.dataTables_filter' ).length ) { 25200 that._blur(); 25201 } 25202 25203 // If the click was inside the DataTables container, don't blur 25204 if ( $(e.target).parents().filter( dt.table().container() ).length ) { 25205 return; 25206 } 25207 25208 // Don't blur in Editor form 25209 if ( $(e.target).parents('div.DTE').length ) { 25210 return; 25211 } 25212 25213 // Or an Editor date input 25214 if ( 25215 $(e.target).parents('div.editor-datetime').length || 25216 $(e.target).parents('div.dt-datetime').length 25217 ) { 25218 return; 25219 } 25220 25221 //If the click was inside the fixed columns container, don't blur 25222 if ( $(e.target).parents().filter('.DTFC_Cloned').length ) { 25223 return; 25224 } 25225 25226 that._blur(); 25227 } ); 25228 } 25229 25230 if ( this.c.editor ) { 25231 var editor = this.c.editor; 25232 25233 // Need to disable KeyTable when the main editor is shown 25234 editor.on( 'open.keyTableMain', function (e, mode, action) {
25235 if ( mode !== 'inline' && that.s.enable ) { 25236 that.enable( false ); 25237 25238 editor.one( 'close'+namespace, function () { 25239 that.enable( true ); 25240 } ); 25241 } 25242 } ); 25243 25244 if ( this.c.editOnFocus ) { 25245 dt.on( 'key-focus'+namespace+' key-refocus'+namespace, function ( e, dt, cell, orig ) { 25246 that._editor( null, orig, true ); 25247 } ); 25248 } 25249 25250 // Activate Editor when a key is pressed (will be ignored, if 25251 // already active). 25252 dt.on( 'key'+namespace, function ( e, dt, key, cell, orig ) { 25253 that._editor( key, orig, false ); 25254 } ); 25255 25256 // Active editing on double click - it will already have focus from 25257 // the click event handler above 25258 $( dt.table().body() ).on( 'dblclick'+namespace, 'th, td', function (e) { 25259 if ( that.s.enable === false ) { 25260 return; 25261 } 25262 25263 var cell = dt.cell( this ); 25264 25265 if ( ! cell.any() ) { 25266 return; 25267 } 25268 25269 if ( that.s.lastFocus && this !== that.s.lastFocus.cell.node() ) { 25270 return; 25271 } 25272 25273 that._editor( null, e, true ); 25274 } ); 25275 25276 // While Editor is busy processing, we don't want to process any key events 25277 editor 25278 .on('preSubmit', function () { 25279 editorBlock = true; 25280 } ) 25281 .on('preSubmitCancelled', function () { 25282 editorBlock = false; 25283 } ) 25284 .on('submitComplete', function () { 25285 editorBlock = false; 25286 } ); 25287 } 25288 25289 // Stave saving 25290 // if ( dt.settings()[0].oFeatures.bStateSave ) { 25291 dt.on( 'stateSaveParams'+namespace, function (e, s, d) { 25292 d.keyTable = that.s.lastFocus ? 25293 that.s.lastFocus.cell.index() : 25294 null; 25295 } ); 25296 // } 25297 25298 dt.on( 'column-visibility'+namespace, function (e) { 25299 that._tabInput(); 25300 } ); 25301 25302 dt.on( 'column-reorder'+namespace, function (e, s, d) { 25303 // Need to update the last focus cell's index 25304 var lastFocus = that.s.lastFocus; 25305 25306 if (lastFocus && lastFocus.cell) { 25307 var curr = lastFocus.relative.column; 25308 25309 // Manipulate the API instance to correct the column index 25310 lastFocus.cell[0][0].column = d.mapping.indexOf(curr); 25311 lastFocus.relative.column = d.mapping.indexOf(curr); 25312 } 25313 } ); 25314 25315 // Redraw - retain focus on the current cell 25316 dt.on( 'draw'+namespace, function (e) { 25317 that._tabInput(); 25318 25319 if ( that.s.focusDraw ) { 25320 return; 25321 } 25322 25323 var lastFocus = that.s.lastFocus; 25324 25325 if ( lastFocus ) { 25326 var relative = that.s.lastFocus.relative; 25327 var info = dt.page.info(); 25328 var row = relative.row + info.start; 25329 25330 if ( info.recordsDisplay === 0 ) { 25331 return; 25332 } 25333 25334 // Reverse if needed 25335 if ( row >= info.recordsDisplay ) { 25336 row = info.recordsDisplay - 1; 25337 } 25338 25339 that._focus( row, relative.column, true, e ); 25340 } 25341 } ); 25342 25343 // Clipboard support 25344 if ( this.c.clipboard ) { 25345 this._clipboard(); 25346 } 25347 25348 dt.on( 'destroy'+namespace, function () { 25349 that._blur( true ); 25350 25351 // Event tidy up 25352 dt.off( namespace ); 25353 25354 $( dt.table().body() ) 25355 .off( 'click'+namespace, 'th, td' ) 25356 .off( 'dblclick'+namespace, 'th, td' ); 25357 25358 $( document ) 25359 .off( 'mousedown'+namespace ) 25360 .off( 'keydown'+namespace ) 25361 .off( 'copy'+namespace ) 25362 .off( 'paste'+namespace ); 25363 } ); 25364 25365 // Initial focus comes from state or options 25366 var state = dt.state.loaded(); 25367 25368 if ( state && state.keyTable ) { 25369 // Wait until init is done 25370 dt.one( 'init', function () { 25371 var cell = dt.cell( state.keyTable ); 25372 25373 // Ensure that the saved cell still exists 25374 if ( cell.any() ) { 25375 cell.focus(); 25376 } 25377 } ); 25378 } 25379 else if ( this.c.focus ) { 25380 dt.cell( this.c.focus ).focus(); 25381 } 25382 }, 25383 25384 25385 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 25386 * Private methods 25387 */ 25388 25389 /** 25390 * Blur the control 25391 * 25392 * @param {boolean} [noEvents=false] Don't trigger updates / events (for destroying) 25393 * @private 25394 */ 25395 _blur: function (noEvents) 25396 { 25397 if ( ! this.s.enable || ! this.s.lastFocus ) { 25398 return; 25399 } 25400 25401 var cell = this.s.lastFocus.cell; 25402 25403 $( cell.node() ).removeClass( this.c.className ); 25404 this.s.lastFocus = null; 25405 25406 if ( ! noEvents ) { 25407 this._updateFixedColumns(cell.index().column); 25408 25409 this._emitEvent( 'key-blur', [ this.s.dt, cell ] ); 25410 } 25411 }, 25412 25413 25414 /** 25415 * Clipboard interaction handlers 25416 * 25417 * @private 25418 */ 25419 _clipboard: function () { 25420 var dt = this.s.dt; 25421 var that = this; 25422 var namespace = this.s.namespace; 25423 25424 // IE8 doesn't support getting selected text 25425 if ( ! window.getSelection ) { 25426 return; 25427 } 25428 25429 $(document).on( 'copy'+namespace, function (ejq) { 25430 var e = ejq.originalEvent; 25431 var selection = window.getSelection().toString(); 25432 var focused = that.s.lastFocus; 25433 25434 // Only copy cell text to clipboard if there is no other selection 25435 // and there is a focused cell 25436 if ( ! selection && focused ) { 25437 e.clipboardData.setData( 25438 'text/plain', 25439 focused.cell.render( that.c.clipboardOrthogonal ) 25440 ); 25441 e.preventDefault(); 25442 } 25443 } ); 25444 25445 $(document).on( 'paste'+namespace, function (ejq) { 25446 var e = ejq.originalEvent; 25447 var focused = that.s.lastFocus; 25448 var activeEl = document.activeElement; 25449 var editor = that.c.editor; 25450 var pastedText; 25451
25452 if ( focused && (! activeEl || activeEl.nodeName.toLowerCase() === 'body') ) { 25453 e.preventDefault(); 25454 25455 if ( window.clipboardData && window.clipboardData.getData ) { 25456 // IE 25457 pastedText = window.clipboardData.getData('Text'); 25458 } 25459 else if ( e.clipboardData && e.clipboardData.getData ) { 25460 // Everything else 25461 pastedText = e.clipboardData.getData('text/plain'); 25462 } 25463 25464 if ( editor ) { 25465 // Got Editor - need to activate inline editing, 25466 // set the value and submit 25467 var options = that._inlineOptions(focused.cell.index()); 25468 25469 editor 25470 .inline(options.cell, options.field, options.options) 25471 .set( editor.displayed()[0], pastedText ) 25472 .submit(); 25473 } 25474 else { 25475 // No editor, so just dump the data in 25476 focused.cell.data( pastedText ); 25477 dt.draw(false); 25478 } 25479 } 25480 } ); 25481 }, 25482 25483 25484 /** 25485 * Get an array of the column indexes that KeyTable can operate on. This 25486 * is a merge of the user supplied columns and the visible columns. 25487 * 25488 * @private 25489 */ 25490 _columns: function () 25491 { 25492 var dt = this.s.dt; 25493 var user = dt.columns( this.c.columns ).indexes(); 25494 var out = []; 25495 25496 dt.columns( ':visible' ).every( function (i) { 25497 if ( user.indexOf( i ) !== -1 ) { 25498 out.push( i ); 25499 } 25500 } ); 25501 25502 return out; 25503 }, 25504 25505 25506 /** 25507 * Perform excel like navigation for Editor by triggering an edit on key 25508 * press 25509 * 25510 * @param {integer} key Key code for the pressed key 25511 * @param {object} orig Original event 25512 * @private 25513 */ 25514 _editor: function ( key, orig, hardEdit ) 25515 { 25516 // If nothing focused, we can't take any action 25517 if (! this.s.lastFocus) { 25518 return; 25519 } 25520 25521 // DataTables draw event 25522 if (orig && orig.type === 'draw') { 25523 return; 25524 } 25525 25526 var that = this; 25527 var dt = this.s.dt; 25528 var editor = this.c.editor; 25529 var editCell = this.s.lastFocus.cell; 25530 var namespace = this.s.namespace + 'e' + editorNamespaceCounter++; 25531 25532 // Do nothing if there is already an inline edit in this cell 25533 if ( $('div.DTE', editCell.node()).length ) { 25534 return; 25535 } 25536 25537 // Don't activate Editor on control key presses 25538 if ( key !== null && ( 25539 (key >= 0x00 && key <= 0x09) || 25540 key === 0x0b || 25541 key === 0x0c || 25542 (key >= 0x0e && key <= 0x1f) || 25543 (key >= 0x70 && key <= 0x7b) || 25544 (key >= 0x7f && key <= 0x9f) 25545 ) ) { 25546 return; 25547 } 25548 25549 if ( orig ) { 25550 orig.stopPropagation(); 25551 25552 // Return key should do nothing - for textareas it would empty the 25553 // contents 25554 if ( key === 13 ) { 25555 orig.preventDefault(); 25556 } 25557 } 25558 25559 var editInline = function () { 25560 var options = that._inlineOptions(editCell.index()); 25561 25562 editor 25563 .one( 'open'+namespace, function () { 25564 // Remove cancel open 25565 editor.off( 'cancelOpen'+namespace ); 25566 25567 // Excel style - select all text 25568 if ( ! hardEdit ) { 25569 $('div.DTE_Field_InputControl input, div.DTE_Field_InputControl textarea').select(); 25570 } 25571 25572 // Reduce the keys the Keys listens for 25573 dt.keys.enable( hardEdit ? 'tab-only' : 'navigation-only' ); 25574 25575 // On blur of the navigation submit 25576 dt.on( 'key-blur.editor', function (e, dt, cell) { 25577 // When Editor has its own blur enabled - do nothing here 25578 if (editor.s.editOpts.onBlur === 'submit') { 25579 return; 25580 } 25581 25582 if ( editor.displayed() && cell.node() === editCell.node() ) { 25583 editor.submit(); 25584 } 25585 } ); 25586 25587 // Highlight the cell a different colour on full edit 25588 if ( hardEdit ) { 25589 $( dt.table().container() ).addClass('dtk-focus-alt'); 25590 } 25591 25592 // If the dev cancels the submit, we need to return focus 25593 editor.on( 'preSubmitCancelled'+namespace, function () { 25594 setTimeout( function () { 25595 that._focus( editCell, null, false ); 25596 }, 50 ); 25597 } ); 25598 25599 editor.on( 'submitUnsuccessful'+namespace, function () { 25600 that._focus( editCell, null, false ); 25601 } ); 25602 25603 // Restore full key navigation on close 25604 editor.one( 'close'+namespace, function () { 25605 dt.keys.enable( true ); 25606 dt.off( 'key-blur.editor' ); 25607 editor.off( namespace ); 25608 $( dt.table().container() ).removeClass('dtk-focus-alt'); 25609 25610 if (that.s.returnSubmit) { 25611 that.s.returnSubmit = false; 25612 that._emitEvent( 'key-return-submit', [dt, editCell] ); 25613 } 25614 } ); 25615 } ) 25616 .one( 'cancelOpen'+namespace, function () { 25617 // `preOpen` can cancel the display of the form, so it 25618 // might be that the open event handler isn't needed 25619 editor.off( namespace ); 25620 } ) 25621 .inline(options.cell, options.field, options.options); 25622 }; 25623 25624 // Editor 1.7 listens for `return` on keyup, so if return is the trigger 25625 // key, we need to wait for `keyup` otherwise Editor would just submit 25626 // the content triggered by this keypress. 25627 if ( key === 13 ) { 25628 hardEdit = true; 25629 25630 $(document).one( 'keyup', function () { // immediately removed 25631 editInline(); 25632 } ); 25633 } 25634 else { 25635 editInline(); 25636 } 25637 }, 25638 25639 25640 _inlineOptions: function (cellIdx) 25641 { 25642 if (this.c.editorOptions) { 25643 return this.c.editorOptions(cellIdx); 25644 } 25645 25646 return { 25647 cell: cellIdx, 25648 field: undefined, 25649 options: undefined 25650 }; 25651 }, 25652 25653 25654 /** 25655 * Emit an event on the DataTable for listeners 25656 * 25657 * @param {string} name Event name 25658 * @param {array} args Event arguments 25659 * @private 25660 */ 25661 _emitEvent: function ( name, args ) 25662 { 25663 return this.s.dt.iterator( 'table', function ( ctx, i ) { 25664 return $(ctx.nTable).triggerHandler( name, args ); 25665 } ); 25666 }, 25667 25668 25669 /** 25670 * Focus on a particular cell, shifting the table's paging if required 25671 * 25672 * @param {DataTables.Api|integer} row Can be given as an API instance that 25673 * contains the cell to focus or as an integer. As the latter it is the 25674 * visible row index (from the whole data set) - NOT the data index 25675 * @param {integer} [column] Not required if a cell is given as the first 25676 * parameter. Otherwise this is the column data index for the cell to 25677 * focus on 25678 * @param {boolean} [shift=true] Should the viewport be moved to show cell 25679 * @private 25680 */ 25681 _focus: function ( row, column, shift, originalEvent ) 25682 { 25683 var that = this; 25684 var dt = this.s.dt; 25685 var pageInfo = dt.page.info(); 25686 var lastFocus = this.s.lastFocus; 25687 25688 if ( ! originalEvent) { 25689 originalEvent = null; 25690 } 25691 25692 if ( ! this.s.enable ) { 25693 return; 25694 } 25695 25696 if ( typeof row !== 'number' ) { 25697 // Its an API instance - check that there is actually a row 25698 if ( ! row.any() ) { 25699 return; 25700 } 25701 25702 // Convert the cell to a row and column 25703 var index = row.index(); 25704 column = index.column; 25705 row = dt 25706 .rows( { filter: 'applied', order: 'applied' } ) 25707 .indexes() 25708 .indexOf( index.row ); 25709 25710 // Don't focus rows that were filtered out. 25711 if ( row < 0 ) { 25712 return; 25713 } 25714 25715 // For server-side processing normalise the row by adding the start 25716 // point, since `rows().indexes()` includes only rows that are 25717 // available at the client-side
25718 if ( pageInfo.serverSide ) { 25719 row += pageInfo.start; 25720 } 25721 } 25722 25723 // Is the row on the current page? If not, we need to redraw to show the 25724 // page 25725 if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) { 25726 this.s.focusDraw = true; 25727 this.s.waitingForDraw = true; 25728 25729 dt 25730 .one( 'draw', function () { 25731 that.s.focusDraw = false; 25732 that.s.waitingForDraw = false; 25733 that._focus( row, column, undefined, originalEvent ); 25734 } ) 25735 .page( Math.floor( row / pageInfo.length ) ) 25736 .draw( false ); 25737 25738 return; 25739 } 25740 25741 // In the available columns? 25742 if ( $.inArray( column, this._columns() ) === -1 ) { 25743 return; 25744 } 25745 25746 // De-normalise the server-side processing row, so we select the row 25747 // in its displayed position 25748 if ( pageInfo.serverSide ) { 25749 row -= pageInfo.start; 25750 } 25751 25752 // Get the cell from the current position - ignoring any cells which might 25753 // not have been rendered (therefore can't use `:eq()` selector). 25754 var cells = dt.cells( null, column, {search: 'applied', order: 'applied'} ).flatten(); 25755 var cell = dt.cell( cells[ row ] ); 25756 25757 // Prefocus check - this event allows a focus action to be disallowed. 25758 var preFocus = this._emitEvent( 'key-prefocus', [ this.s.dt, cell, originalEvent || null ] ); 25759 if (preFocus.indexOf(false) !== -1) { 25760 return; 25761 } 25762 25763 if ( lastFocus ) { 25764 // Don't trigger a refocus on the same cell 25765 if ( lastFocus.node === cell.node() ) { 25766 this._emitEvent( 'key-refocus', [ this.s.dt, cell, originalEvent || null ] ); 25767 return; 25768 } 25769 25770 // Otherwise blur the old focus 25771 this._blur(); 25772 } 25773 25774 // Clear focus from other tables 25775 this._removeOtherFocus(); 25776 25777 var node = $( cell.node() ); 25778 node.addClass( this.c.className ); 25779 25780 this._updateFixedColumns(column); 25781 25782 // Shift viewpoint and page to make cell visible 25783 if ( shift === undefined || shift === true ) { 25784 this._scroll( $(window), $(document.body), node, 'offset' ); 25785 25786 var bodyParent = dt.table().body().parentNode; 25787 if ( bodyParent !== dt.table().header().parentNode ) { 25788 var parent = $(bodyParent.parentNode); 25789 25790 this._scroll( parent, parent, node, 'position' ); 25791 } 25792 } 25793 25794 // Event and finish 25795 this.s.lastFocus = { 25796 cell: cell, 25797 node: cell.node(), 25798 relative: { 25799 row: dt.rows( { page: 'current' } ).indexes().indexOf( cell.index().row ), 25800 column: cell.index().column 25801 } 25802 }; 25803 25804 this._emitEvent( 'key-focus', [ this.s.dt, cell, originalEvent || null ] ); 25805 dt.state.save(); 25806 }, 25807 25808 /** 25809 * Handle key press 25810 * 25811 * @param {object} e Event 25812 * @private 25813 */ 25814 _key: function ( e ) 25815 { 25816 // If we are waiting for a draw to happen from another key event, then 25817 // do nothing for this new key press. 25818 if ( this.s.waitingForDraw ) { 25819 e.preventDefault(); 25820 return; 25821 } 25822 25823 var enable = this.s.enable; 25824 this.s.returnSubmit = (enable === 'navigation-only' || enable === 'tab-only') && e.keyCode === 13 25825 ? true 25826 : false; 25827 25828 var navEnable = enable === true || enable === 'navigation-only'; 25829 if ( ! enable ) { 25830 return; 25831 } 25832 25833 if ( (e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey) && !(e.ctrlKey && e.altKey) ) { 25834 return; 25835 } 25836 25837 // If not focused, then there is no key action to take 25838 var lastFocus = this.s.lastFocus; 25839 if ( ! lastFocus ) { 25840 return; 25841 } 25842 25843 // And the last focus still exists! 25844 if ( ! this.s.dt.cell(lastFocus.node).any() ) { 25845 this.s.lastFocus = null; 25846 return; 25847 } 25848 25849 var that = this; 25850 var dt = this.s.dt; 25851 var scrolling = this.s.dt.settings()[0].oScroll.sY ? true : false; 25852 25853 // If we are not listening for this key, do nothing 25854 if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) { 25855 return; 25856 } 25857 25858 switch( e.keyCode ) { 25859 case 9: // tab 25860 // `enable` can be tab-only 25861 e.preventDefault(); 25862 25863 this._keyAction( function () { 25864 that._shift( e, e.shiftKey ? 'left' : 'right', true ); 25865 } ); 25866 break; 25867 25868 case 27: // esc 25869 if ( this.c.blurable && enable === true ) { 25870 this._blur(); 25871 } 25872 break; 25873 25874 case 33: // page up (previous page) 25875 case 34: // page down (next page)
25876 if ( navEnable && !scrolling ) { 25877 e.preventDefault(); 25878 25879 this._keyAction( function () { 25880 dt 25881 .page( e.keyCode === 33 ? 'previous' : 'next' ) 25882 .draw( false ); 25883 } ); 25884 } 25885 break; 25886 25887 case 35: // end (end of current page) 25888 case 36: // home (start of current page) 25889 if ( navEnable ) { 25890 e.preventDefault(); 25891 25892 this._keyAction( function () { 25893 var indexes = dt.cells( {page: 'current'} ).indexes(); 25894 var colIndexes = that._columns(); 25895 25896 that._focus( dt.cell( 25897 indexes[ e.keyCode === 35 ? indexes.length-1 : colIndexes[0] ] 25898 ), null, true, e ); 25899 } ); 25900 } 25901 break; 25902 25903 case 37: // left arrow 25904 if ( navEnable ) { 25905 this._keyAction( function () { 25906 that._shift( e, 'left' ); 25907 } ); 25908 } 25909 break; 25910 25911 case 38: // up arrow 25912 if ( navEnable ) { 25913 this._keyAction( function () { 25914 that._shift( e, 'up' ); 25915 } ); 25916 } 25917 break; 25918 25919 case 39: // right arrow 25920 if ( navEnable ) { 25921 this._keyAction( function () { 25922 that._shift( e, 'right' ); 25923 } ); 25924 } 25925 break; 25926 25927 case 40: // down arrow 25928 if ( navEnable ) { 25929 this._keyAction( function () { 25930 that._shift( e, 'down' ); 25931 } ); 25932 } 25933 break; 25934 25935 case 113: // F2 - Excel like hard edit 25936 if ( this.c.editor ) { 25937 this._editor(null, e, true); 25938 break; 25939 } 25940 // else fallthrough 25941 25942 default: 25943 // Everything else - pass through only when fully enabled 25944 if ( enable === true ) { 25945 this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus.cell, e ] ); 25946 } 25947 break; 25948 } 25949 }, 25950 25951 /** 25952 * Whether we perform a key shift action immediately or not depends 25953 * upon if Editor is being used. If it is, then we wait until it 25954 * completes its action 25955 * @param {*} action Function to trigger when ready 25956 */ 25957 _keyAction: function (action) { 25958 var editor = this.c.editor; 25959 25960 if (editor && editor.mode()) { 25961 editor.submit(action); 25962 } 25963 else { 25964 action(); 25965 } 25966 }, 25967 25968 /** 25969 * Remove focus from all tables other than this one 25970 */ 25971 _removeOtherFocus: function () 25972 { 25973 var thisTable = this.s.dt.table().node(); 25974 25975 $.fn.dataTable.tables({api:true}).iterator('table', function (settings) { 25976 if (this.table().node() !== thisTable) { 25977 this.cell.blur(); 25978 } 25979 }); 25980 }, 25981 25982 /** 25983 * Scroll a container to make a cell visible in it. This can be used for 25984 * both DataTables scrolling and native window scrolling. 25985 * 25986 * @param {jQuery} container Scrolling container 25987 * @param {jQuery} scroller Item being scrolled 25988 * @param {jQuery} cell Cell in the scroller 25989 * @param {string} posOff `position` or `offset` - which to use for the 25990 * calculation. `offset` for the document, otherwise `position` 25991 * @private 25992 */ 25993 _scroll: function ( container, scroller, cell, posOff ) 25994 { 25995 var offset = cell[posOff](); 25996 var height = cell.outerHeight(); 25997 var width = cell.outerWidth(); 25998 25999 var scrollTop = scroller.scrollTop(); 26000 var scrollLeft = scroller.scrollLeft(); 26001 var containerHeight = container.height(); 26002 var containerWidth = container.width(); 26003 26004 // If Scroller is being used, the table can be `position: absolute` and that 26005 // needs to be taken account of in the offset. If no Scroller, this will be 0 26006 if ( posOff === 'position' ) { 26007 offset.top += parseInt( cell.closest('table').css('top'), 10 ); 26008 } 26009 26010 // Top correction (partially in view) 26011 if ( offset.top < scrollTop && offset.top + height > scrollTop - 5 ) { 26012 scroller.scrollTop( offset.top ); 26013 } 26014 26015 // Left correction 26016 if ( offset.left < scrollLeft ) { 26017 scroller.scrollLeft( offset.left ); 26018 } 26019 26020 // Bottom correction plus in view correction. Note that the magic 5 is to allow 26021 // for the edge just passing the bottom of the view 26022 if ( 26023 offset.top + height > scrollTop + containerHeight && 26024 offset.top < scrollTop + containerHeight + 5 && 26025 height < containerHeight 26026 ) { 26027 scroller.scrollTop( offset.top + height - containerHeight ); 26028 } 26029 26030 // Right correction 26031 if ( offset.left + width > scrollLeft + containerWidth && width < containerWidth ) { 26032 scroller.scrollLeft( offset.left + width - containerWidth ); 26033 } 26034 }, 26035 26036 26037 /** 26038 * Calculate a single offset movement in the table - up, down, left and 26039 * right and then perform the focus if possible 26040 * 26041 * @param {object} e Event object 26042 * @param {string} direction Movement direction 26043 * @param {boolean} keyBlurable `true` if the key press can result in the 26044 * table being blurred. This is so arrow keys won't blur the table, but 26045 * tab will. 26046 * @private 26047 */ 26048 _shift: function ( e, direction, keyBlurable ) 26049 { 26050 var that = this; 26051 var dt = this.s.dt; 26052 var pageInfo = dt.page.info(); 26053 var rows = pageInfo.recordsDisplay; 26054 var columns = this._columns(); 26055 var last = this.s.lastFocus; 26056 if ( ! last ) { 26057 return; 26058 } 26059 26060 var currentCell = last.cell; 26061 if ( ! currentCell ) { 26062 return; 26063 } 26064 26065 var currRow = dt 26066 .rows( { filter: 'applied', order: 'applied' } ) 26067 .indexes() 26068 .indexOf( currentCell.index().row ); 26069 26070 // When server-side processing, `rows().indexes()` only gives the rows 26071 // that are available at the client-side, so we need to normalise the 26072 // row's current position by the display start point 26073 if ( pageInfo.serverSide ) { 26074 currRow += pageInfo.start; 26075 } 26076 26077 var currCol = dt 26078 .columns( columns ) 26079 .indexes() 26080 .indexOf( currentCell.index().column ); 26081 26082 var 26083 row = currRow, 26084 column = columns[ currCol ]; // row is the display, column is an index 26085 26086 // If the direction is rtl then the logic needs to be inverted from this point forwards 26087 if($(dt.table().node()).css('direction') === 'rtl') { 26088 if(direction === 'right') { 26089 direction = 'left'; 26090 } 26091 else if(direction === 'left'){ 26092 direction = 'right'; 26093 } 26094 } 26095 26096 if ( direction === 'right' ) { 26097 if ( currCol >= columns.length - 1 ) { 26098 row++; 26099 column = columns[0]; 26100 } 26101 else { 26102 column = columns[ currCol+1 ]; 26103 } 26104 } 26105 else if ( direction === 'left' ) { 26106 if ( currCol === 0 ) { 26107 row--; 26108 column = columns[ columns.length - 1 ]; 26109 } 26110 else { 26111 column = columns[ currCol-1 ]; 26112 } 26113 } 26114 else if ( direction === 'up' ) { 26115 row--; 26116 } 26117 else if ( direction === 'down' ) { 26118 row++; 26119 } 26120 26121 if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1 ) { 26122 if (e) { 26123 e.preventDefault(); 26124 } 26125 26126 this._focus( row, column, true, e ); 26127 } 26128 else if ( ! keyBlurable || ! this.c.blurable ) {
26129 // No new focus, but if the table isn't blurable, then don't loose 26130 // focus 26131 if (e) { 26132 e.preventDefault(); 26133 } 26134 } 26135 else { 26136 this._blur(); 26137 } 26138 }, 26139 26140 26141 /** 26142 * Create and insert a hidden input element that can receive focus on behalf 26143 * of the table 26144 * 26145 * @private 26146 */ 26147 _tabInput: function () 26148 { 26149 var that = this; 26150 var dt = this.s.dt; 26151 var tabIndex = this.c.tabIndex !== null ? 26152 this.c.tabIndex : 26153 dt.settings()[0].iTabIndex; 26154 26155 if ( tabIndex == -1 ) { 26156 return; 26157 } 26158 26159 // Only create the input element once on first class 26160 if (! this.s.tabInput) { 26161 var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>') 26162 .css( { 26163 position: 'absolute', 26164 height: 1, 26165 width: 0, 26166 overflow: 'hidden' 26167 } ); 26168 26169 div.children().on( 'focus', function (e) { 26170 var cell = dt.cell(':eq(0)', that._columns(), {page: 'current'}); 26171 26172 if ( cell.any() ) { 26173 that._focus( cell, null, true, e ); 26174 } 26175 } ); 26176 26177 this.s.tabInput = div; 26178 } 26179 26180 // Insert the input element into the first cell in the table's body 26181 var cell = this.s.dt.cell(':eq(0)', '0:visible', {page: 'current', order: 'current'}).node(); 26182 if (cell) { 26183 $(cell).prepend(this.s.tabInput); 26184 } 26185 }, 26186 26187 /** 26188 * Update fixed columns if they are enabled and if the cell we are 26189 * focusing is inside a fixed column 26190 * @param {integer} column Index of the column being changed 26191 * @private 26192 */ 26193 _updateFixedColumns: function( column ) 26194 { 26195 var dt = this.s.dt; 26196 var settings = dt.settings()[0]; 26197 26198 if ( settings._oFixedColumns ) { 26199 var leftCols = settings._oFixedColumns.s.iLeftColumns; 26200 var rightCols = settings.aoColumns.length - settings._oFixedColumns.s.iRightColumns; 26201 26202 if (column < leftCols || column >= rightCols) { 26203 dt.fixedColumns().update(); 26204 } 26205 } 26206 } 26207} ); 26208 26209 26210/** 26211 * KeyTable default settings for initialisation 26212 * 26213 * @namespace 26214 * @name KeyTable.defaults 26215 * @static 26216 */ 26217KeyTable.defaults = { 26218 /** 26219 * Can focus be removed from the table 26220 * @type {Boolean} 26221 */ 26222 blurable: true, 26223 26224 /** 26225 * Class to give to the focused cell 26226 * @type {String} 26227 */ 26228 className: 'focus', 26229 26230 /** 26231 * Enable or disable clipboard support 26232 * @type {Boolean} 26233 */ 26234 clipboard: true, 26235 26236 /** 26237 * Orthogonal data that should be copied to clipboard 26238 * @type {string} 26239 */ 26240 clipboardOrthogonal: 'display', 26241 26242 /** 26243 * Columns that can be focused. This is automatically merged with the 26244 * visible columns as only visible columns can gain focus. 26245 * @type {String} 26246 */ 26247 columns: '', // all 26248 26249 /** 26250 * Editor instance to automatically perform Excel like navigation 26251 * @type {Editor} 26252 */ 26253 editor: null, 26254 26255 /** 26256 * Trigger editing immediately on focus 26257 * @type {boolean} 26258 */ 26259 editOnFocus: false, 26260 26261 /** 26262 * Options to pass to Editor's inline method 26263 * @type {function} 26264 */ 26265 editorOptions: null, 26266 26267 /** 26268 * Select a cell to automatically select on start up. `null` for no 26269 * automatic selection 26270 * @type {cell-selector} 26271 */ 26272 focus: null, 26273 26274 /** 26275 * Array of keys to listen for 26276 * @type {null|array} 26277 */ 26278 keys: null, 26279 26280 /** 26281 * Tab index for where the table should sit in the document's tab flow 26282 * @type {integer|null} 26283 */ 26284 tabIndex: null 26285}; 26286 26287 26288 26289KeyTable.version = "2.9.0"; 26290 26291 26292$.fn.dataTable.KeyTable = KeyTable; 26293$.fn.DataTable.KeyTable = KeyTable; 26294 26295 26296DataTable.Api.register( 'cell.blur()', function () { 26297 return this.iterator( 'table', function (ctx) { 26298 if ( ctx.keytable ) { 26299 ctx.keytable.blur(); 26300 } 26301 } ); 26302} ); 26303 26304DataTable.Api.register( 'cell().focus()', function () { 26305 return this.iterator( 'cell', function (ctx, row, column) { 26306 if ( ctx.keytable ) { 26307 ctx.keytable.focus( row, column ); 26308 } 26309 } ); 26310} ); 26311 26312DataTable.Api.register( 'keys.disable()', function () { 26313 return this.iterator( 'table', function (ctx) { 26314 if ( ctx.keytable ) { 26315 ctx.keytable.enable( false ); 26316 } 26317 } ); 26318} ); 26319 26320DataTable.Api.register( 'keys.enable()', function ( opts ) { 26321 return this.iterator( 'table', function (ctx) { 26322 if ( ctx.keytable ) { 26323 ctx.keytable.enable( opts === undefined ? true : opts ); 26324 } 26325 } ); 26326} ); 26327
26328DataTable.Api.register( 'keys.enabled()', function ( opts ) { 26329 var ctx = this.context; 26330 26331 if (ctx.length) { 26332 return ctx[0].keytable 26333 ? ctx[0].keytable.enabled() 26334 : false; 26335 } 26336 26337 return false; 26338} ); 26339 26340DataTable.Api.register( 'keys.move()', function ( dir ) { 26341 return this.iterator( 'table', function (ctx) { 26342 if ( ctx.keytable ) { 26343 ctx.keytable._shift( null, dir, false ); 26344 } 26345 } ); 26346} ); 26347 26348// Cell selector 26349DataTable.ext.selector.cell.push( function ( settings, opts, cells ) { 26350 var focused = opts.focused; 26351 var kt = settings.keytable; 26352 var out = []; 26353 26354 if ( ! kt || focused === undefined ) { 26355 return cells; 26356 } 26357 26358 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 26359 if ( (focused === true && kt.focused( cells[i] ) ) || 26360 (focused === false && ! kt.focused( cells[i] ) ) 26361 ) { 26362 out.push( cells[i] ); 26363 } 26364 } 26365 26366 return out; 26367} ); 26368 26369 26370// Attach a listener to the document which listens for DataTables initialisation 26371// events so we can automatically initialise 26372$(document).on( 'preInit.dt.dtk', function (e, settings, json) { 26373 if ( e.namespace !== 'dt' ) { 26374 return; 26375 } 26376 26377 var init = settings.oInit.keys; 26378 var defaults = DataTable.defaults.keys; 26379 26380 if ( init || defaults ) { 26381 var opts = $.extend( {}, defaults, init ); 26382 26383 if ( init !== false ) { 26384 new KeyTable( settings, opts ); 26385 } 26386 } 26387} ); 26388 26389 26390return DataTable; 26391})); 26392 26393 26394/*! Responsive 2.4.1 26395 * © SpryMedia Ltd - datatables.net/license 26396 */ 26397 26398(function( factory ){ 26399 if ( typeof define === 'function' && define.amd ) { 26400 // AMD 26401 define( ['jquery', 'datatables.net'], function ( $ ) { 26402 return factory( $, window, document ); 26403 } ); 26404 } 26405 else if ( typeof exports === 'object' ) { 26406 // CommonJS 26407 var jq = require('jquery'); 26408 var cjsRequires = function (root, $) { 26409 if ( ! $.fn.dataTable ) { 26410 require('datatables.net')(root, $); 26411 } 26412 }; 26413 26414 if (typeof window !== 'undefined') { 26415 module.exports = function (root, $) { 26416 if ( ! root ) { 26417 // CommonJS environments without a window global must pass a 26418 // root. This will give an error otherwise 26419 root = window; 26420 } 26421 26422 if ( ! $ ) { 26423 $ = jq( root ); 26424 } 26425 26426 cjsRequires( root, $ ); 26427 return factory( $, root, root.document ); 26428 }; 26429 } 26430 else { 26431 cjsRequires( window, jq ); 26432 module.exports = factory( jq, window, window.document ); 26433 } 26434 } 26435 else { 26436 // Browser 26437 factory( jQuery, window, document ); 26438 } 26439}(function( $, window, document, undefined ) { 26440'use strict'; 26441var DataTable = $.fn.dataTable; 26442 26443 26444 26445/** 26446 * @summary Responsive 26447 * @description Responsive tables plug-in for DataTables 26448 * @version 2.4.1 26449 * @author SpryMedia Ltd (www.sprymedia.co.uk) 26450 * @contact www.sprymedia.co.uk/contact 26451 * @copyright SpryMedia Ltd. 26452 * 26453 * This source file is free software, available under the following license: 26454 * MIT license - http://datatables.net/license/mit 26455 * 26456 * This source file is distributed in the hope that it will be useful, but 26457 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 26458 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 26459 * 26460 * For details please refer to: http://www.datatables.net 26461 */ 26462 26463/** 26464 * Responsive is a plug-in for the DataTables library that makes use of 26465 * DataTables' ability to change the visibility of columns, changing the 26466 * visibility of columns so the displayed columns fit into the table container. 26467 * The end result is that complex tables will be dynamically adjusted to fit 26468 * into the viewport, be it on a desktop, tablet or mobile browser. 26469 * 26470 * Responsive for DataTables has two modes of operation, which can used 26471 * individually or combined: 26472 * 26473 * * Class name based control - columns assigned class names that match the 26474 * breakpoint logic can be shown / hidden as required for each breakpoint. 26475 * * Automatic control - columns are automatically hidden when there is no 26476 * room left to display them. Columns removed from the right. 26477 * 26478 * In additional to column visibility control, Responsive also has built into 26479 * options to use DataTables' child row display to show / hide the information 26480 * from the table that has been hidden. There are also two modes of operation 26481 * for this child row display: 26482 * 26483 * * Inline - when the control element that the user can use to show / hide 26484 * child rows is displayed inside the first column of the table. 26485 * * Column - where a whole column is dedicated to be the show / hide control. 26486 * 26487 * Initialisation of Responsive is performed by: 26488 * 26489 * * Adding the class `responsive` or `dt-responsive` to the table. In this case 26490 * Responsive will automatically be initialised with the default configuration 26491 * options when the DataTable is created. 26492 * * Using the `responsive` option in the DataTables configuration options. This 26493 * can also be used to specify the configuration options, or simply set to 26494 * `true` to use the defaults. 26495 * 26496 * @class 26497 * @param {object} settings DataTables settings object for the host table 26498 * @param {object} [opts] Configuration options 26499 * @requires jQuery 1.7+ 26500 * @requires DataTables 1.10.3+ 26501 * 26502 * @example 26503 * $('#example').DataTable( { 26504 * responsive: true 26505 * } ); 26506 * } ); 26507 */ 26508var Responsive = function ( settings, opts ) { 26509 // Sanity check that we are using DataTables 1.10 or newer
26510 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.10' ) ) { 26511 throw 'DataTables Responsive requires DataTables 1.10.10 or newer'; 26512 } 26513 26514 this.s = { 26515 childNodeStore: {}, 26516 columns: [], 26517 current: [], 26518 dt: new DataTable.Api( settings ) 26519 }; 26520 26521 // Check if responsive has already been initialised on this table 26522 if ( this.s.dt.settings()[0].responsive ) { 26523 return; 26524 } 26525 26526 // details is an object, but for simplicity the user can give it as a string 26527 // or a boolean 26528 if ( opts && typeof opts.details === 'string' ) { 26529 opts.details = { type: opts.details }; 26530 } 26531 else if ( opts && opts.details === false ) { 26532 opts.details = { type: false }; 26533 } 26534 else if ( opts && opts.details === true ) { 26535 opts.details = { type: 'inline' }; 26536 } 26537 26538 this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts ); 26539 settings.responsive = this; 26540 this._constructor(); 26541}; 26542 26543$.extend( Responsive.prototype, { 26544 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 26545 * Constructor 26546 */ 26547 26548 /** 26549 * Initialise the Responsive instance 26550 * 26551 * @private 26552 */ 26553 _constructor: function () 26554 { 26555 var that = this; 26556 var dt = this.s.dt; 26557 var dtPrivateSettings = dt.settings()[0]; 26558 var oldWindowWidth = $(window).innerWidth(); 26559 26560 dt.settings()[0]._responsive = this; 26561 26562 // Use DataTables' throttle function to avoid processor thrashing on 26563 // resize 26564 $(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () { 26565 // iOS has a bug whereby resize can fire when only scrolling 26566 // See: http://stackoverflow.com/questions/8898412 26567 var width = $(window).innerWidth(); 26568 26569 if ( width !== oldWindowWidth ) { 26570 that._resize(); 26571 oldWindowWidth = width; 26572 } 26573 } ) ); 26574 26575 // DataTables doesn't currently trigger an event when a row is added, so 26576 // we need to hook into its private API to enforce the hidden rows when 26577 // new data is added 26578 dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) { 26579 if ( $.inArray( false, that.s.current ) !== -1 ) { 26580 $('>td, >th', tr).each( function ( i ) { 26581 var idx = dt.column.index( 'toData', i ); 26582 26583 if ( that.s.current[idx] === false ) { 26584 $(this).css('display', 'none'); 26585 } 26586 } ); 26587 } 26588 } ); 26589 26590 // Destroy event handler 26591 dt.on( 'destroy.dtr', function () { 26592 dt.off( '.dtr' ); 26593 $( dt.table().body() ).off( '.dtr' ); 26594 $(window).off( 'resize.dtr orientationchange.dtr' ); 26595 dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control'); 26596 26597 // Restore the columns that we've hidden 26598 $.each( that.s.current, function ( i, val ) { 26599 if ( val === false ) { 26600 that._setColumnVis( i, true ); 26601 } 26602 } ); 26603 } ); 26604 26605 // Reorder the breakpoints array here in case they have been added out 26606 // of order 26607 this.c.breakpoints.sort( function (a, b) { 26608 return a.width < b.width ? 1 : 26609 a.width > b.width ? -1 : 0; 26610 } ); 26611 26612 this._classLogic(); 26613 this._resizeAuto(); 26614 26615 // Details handler 26616 var details = this.c.details; 26617 26618 if ( details.type !== false ) { 26619 that._detailsInit(); 26620 26621 // DataTables will trigger this event on every column it shows and 26622 // hides individually 26623 dt.on( 'column-visibility.dtr', function () { 26624 // Use a small debounce to allow multiple columns to be set together 26625 if ( that._timer ) { 26626 clearTimeout( that._timer ); 26627 } 26628 26629 that._timer = setTimeout( function () { 26630 that._timer = null; 26631 26632 that._classLogic(); 26633 that._resizeAuto(); 26634 that._resize(true); 26635 26636 that._redrawChildren(); 26637 }, 100 ); 26638 } ); 26639 26640 // Redraw the details box on each draw which will happen if the data 26641 // has changed. This is used until DataTables implements a native 26642 // `updated` event for rows 26643 dt.on( 'draw.dtr', function () { 26644 that._redrawChildren(); 26645 } ); 26646 26647 $(dt.table().node()).addClass( 'dtr-'+details.type ); 26648 } 26649 26650 dt.on( 'column-reorder.dtr', function (e, settings, details) { 26651 that._classLogic(); 26652 that._resizeAuto(); 26653 that._resize(true); 26654 } ); 26655 26656 // Change in column sizes means we need to calc 26657 dt.on( 'column-sizing.dtr', function () { 26658 that._resizeAuto(); 26659 that._resize(); 26660 }); 26661 26662 // DT2 let's us tell it if we are hiding columns 26663 dt.on( 'column-calc.dt', function (e, d) { 26664 var curr = that.s.current; 26665 26666 for (var i=0 ; i<curr.length ; i++) { 26667 var idx = d.visible.indexOf(i); 26668 26669 if (curr[i] === false && idx >= 0) { 26670 d.visible.splice(idx, 1); 26671 } 26672 } 26673 } ); 26674 26675 // On Ajax reload we want to reopen any child rows which are displayed 26676 // by responsive 26677 dt.on( 'preXhr.dtr', function () { 26678 var rowIds = []; 26679 dt.rows().every( function () { 26680 if ( this.child.isShown() ) { 26681 rowIds.push( this.id(true) ); 26682 } 26683 } ); 26684 26685 dt.one( 'draw.dtr', function () { 26686 that._resizeAuto(); 26687 that._resize(); 26688 26689 dt.rows( rowIds ).every( function () { 26690 that._detailsDisplay( this, false ); 26691 } ); 26692 } ); 26693 }); 26694 26695 dt 26696 .on( 'draw.dtr', function () { 26697 that._controlClass(); 26698 }) 26699 .on( 'init.dtr', function (e, settings, details) { 26700 if ( e.namespace !== 'dt' ) { 26701 return; 26702 } 26703 26704 that._resizeAuto(); 26705 that._resize(); 26706 26707 // If columns were hidden, then DataTables needs to adjust the 26708 // column sizing 26709 if ( $.inArray( false, that.s.current ) ) { 26710 dt.columns.adjust(); 26711 } 26712 } ); 26713 26714 // First pass - draw the table for the current viewport size 26715 this._resize(); 26716 }, 26717 26718 26719 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 26720 * Private methods 26721 */ 26722 26723 /** 26724 * Get and store nodes from a cell - use for node moving renderers 26725 * 26726 * @param {*} dt DT instance 26727 * @param {*} row Row index 26728 * @param {*} col Column index 26729 */ 26730 _childNodes: function( dt, row, col ) { 26731 var name = row+'-'+col; 26732 26733 if ( this.s.childNodeStore[ name ] ) { 26734 return this.s.childNodeStore[ name ]; 26735 } 26736 26737 // https://jsperf.com/childnodes-array-slice-vs-loop 26738 var nodes = []; 26739 var children = dt.cell( row, col ).node().childNodes; 26740 for ( var i=0, ien=children.length ; i<ien ; i++ ) { 26741 nodes.push( children[i] ); 26742 } 26743 26744 this.s.childNodeStore[ name ] = nodes; 26745 26746 return nodes; 26747 }, 26748 26749 /** 26750 * Restore nodes from the cache to a table cell 26751 * 26752 * @param {*} dt DT instance 26753 * @param {*} row Row index 26754 * @param {*} col Column index 26755 */ 26756 _childNodesRestore: function( dt, row, col ) { 26757 var name = row+'-'+col; 26758 26759 if ( ! this.s.childNodeStore[ name ] ) { 26760 return; 26761 } 26762 26763 var node = dt.cell( row, col ).node(); 26764 var store = this.s.childNodeStore[ name ]; 26765 var parent = store[0].parentNode; 26766 var parentChildren = parent.childNodes; 26767 var a = []; 26768 26769 for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) { 26770 a.push( parentChildren[i] ); 26771 } 26772 26773 for ( var j=0, jen=a.length ; j<jen ; j++ ) { 26774 node.appendChild( a[j] ); 26775 } 26776 26777 this.s.childNodeStore[ name ] = undefined; 26778 }, 26779 26780 /** 26781 * Calculate the visibility for the columns in a table for a given 26782 * breakpoint. The result is pre-determined based on the class logic if 26783 * class names are used to control all columns, but the width of the table 26784 * is also used if there are columns which are to be automatically shown 26785 * and hidden. 26786 * 26787 * @param {string} breakpoint Breakpoint name to use for the calculation 26788 * @return {array} Array of boolean values initiating the visibility of each 26789 * column. 26790 * @private 26791 */ 26792 _columnsVisiblity: function ( breakpoint ) 26793 { 26794 var dt = this.s.dt; 26795 var columns = this.s.columns; 26796 var i, ien; 26797 26798 // Create an array that defines the column ordering based first on the 26799 // column's priority, and secondly the column index. This allows the 26800 // columns to be removed from the right if the priority matches 26801 var order = columns 26802 .map( function ( col, idx ) { 26803 return { 26804 columnIdx: idx, 26805 priority: col.priority 26806 }; 26807 } ) 26808 .sort( function ( a, b ) { 26809 if ( a.priority !== b.priority ) { 26810 return a.priority - b.priority; 26811 } 26812 return a.columnIdx - b.columnIdx; 26813 } ); 26814 26815 // Class logic - determine which columns are in this breakpoint based 26816 // on the classes. If no class control (i.e. `auto`) then `-` is used 26817 // to indicate this to the rest of the function 26818 var display = $.map( columns, function ( col, i ) { 26819 if ( dt.column(i).visible() === false ) { 26820 return 'not-visible'; 26821 } 26822 return col.auto && col.minWidth === null ? 26823 false : 26824 col.auto === true ? 26825 '-' : 26826 $.inArray( breakpoint, col.includeIn ) !== -1; 26827 } ); 26828 26829 // Auto column control - first pass: how much width is taken by the 26830 // ones that must be included from the non-auto columns 26831 var requiredWidth = 0; 26832 for ( i=0, ien=display.length ; i<ien ; i++ ) { 26833 if ( display[i] === true ) { 26834 requiredWidth += columns[i].minWidth; 26835 } 26836 } 26837 26838 // Second pass, use up any remaining width for other columns. For 26839 // scrolling tables we need to subtract the width of the scrollbar. It
26840 // may not be requires which makes this sub-optimal, but it would 26841 // require another full redraw to make complete use of those extra few 26842 // pixels 26843 var scrolling = dt.settings()[0].oScroll; 26844 var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0; 26845 var widthAvailable = dt.table().container().offsetWidth - bar; 26846 var usedWidth = widthAvailable - requiredWidth; 26847 26848 // Control column needs to always be included. This makes it sub- 26849 // optimal in terms of using the available with, but to stop layout 26850 // thrashing or overflow. Also we need to account for the control column 26851 // width first so we know how much width is available for the other 26852 // columns, since the control column might not be the first one shown 26853 for ( i=0, ien=display.length ; i<ien ; i++ ) { 26854 if ( columns[i].control ) { 26855 usedWidth -= columns[i].minWidth; 26856 } 26857 } 26858 26859 // Allow columns to be shown (counting by priority and then right to 26860 // left) until we run out of room 26861 var empty = false; 26862 for ( i=0, ien=order.length ; i<ien ; i++ ) { 26863 var colIdx = order[i].columnIdx; 26864 26865 if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) { 26866 // Once we've found a column that won't fit we don't let any 26867 // others display either, or columns might disappear in the 26868 // middle of the table 26869 if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) { 26870 empty = true; 26871 display[colIdx] = false; 26872 } 26873 else { 26874 display[colIdx] = true; 26875 } 26876 26877 usedWidth -= columns[colIdx].minWidth; 26878 } 26879 } 26880 26881 // Determine if the 'control' column should be shown (if there is one). 26882 // This is the case when there is a hidden column (that is not the 26883 // control column). The two loops look inefficient here, but they are 26884 // trivial and will fly through. We need to know the outcome from the 26885 // first , before the action in the second can be taken 26886 var showControl = false; 26887 26888 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 26889 if ( ! columns[i].control && ! columns[i].never && display[i] === false ) { 26890 showControl = true; 26891 break; 26892 } 26893 } 26894 26895 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 26896 if ( columns[i].control ) { 26897 display[i] = showControl; 26898 } 26899 26900 // Replace not visible string with false from the control column detection above 26901 if ( display[i] === 'not-visible' ) { 26902 display[i] = false; 26903 } 26904 } 26905 26906 // Finally we need to make sure that there is at least one column that 26907 // is visible 26908 if ( $.inArray( true, display ) === -1 ) { 26909 display[0] = true; 26910 } 26911
26912 return display; 26913 }, 26914 26915 26916 /** 26917 * Create the internal `columns` array with information about the columns 26918 * for the table. This includes determining which breakpoints the column 26919 * will appear in, based upon class names in the column, which makes up the 26920 * vast majority of this method. 26921 * 26922 * @private 26923 */ 26924 _classLogic: function () 26925 { 26926 var that = this; 26927 var calc = {}; 26928 var breakpoints = this.c.breakpoints; 26929 var dt = this.s.dt; 26930 var columns = dt.columns().eq(0).map( function (i) { 26931 var column = this.column(i); 26932 var className = column.header().className; 26933 var priority = dt.settings()[0].aoColumns[i].responsivePriority; 26934 var dataPriority = column.header().getAttribute('data-priority'); 26935 26936 if ( priority === undefined ) { 26937 priority = dataPriority === undefined || dataPriority === null? 26938 10000 : 26939 dataPriority * 1; 26940 } 26941 26942 return { 26943 className: className, 26944 includeIn: [], 26945 auto: false, 26946 control: false, 26947 never: className.match(/\b(dtr\-)?never\b/) ? true : false, 26948 priority: priority 26949 }; 26950 } ); 26951 26952 // Simply add a breakpoint to `includeIn` array, ensuring that there are 26953 // no duplicates 26954 var add = function ( colIdx, name ) { 26955 var includeIn = columns[ colIdx ].includeIn; 26956 26957 if ( $.inArray( name, includeIn ) === -1 ) { 26958 includeIn.push( name ); 26959 } 26960 }; 26961 26962 var column = function ( colIdx, name, operator, matched ) { 26963 var size, i, ien; 26964 26965 if ( ! operator ) { 26966 columns[ colIdx ].includeIn.push( name ); 26967 } 26968 else if ( operator === 'max-' ) { 26969 // Add this breakpoint and all smaller 26970 size = that._find( name ).width; 26971 26972 for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) { 26973 if ( breakpoints[i].width <= size ) { 26974 add( colIdx, breakpoints[i].name ); 26975 } 26976 } 26977 } 26978 else if ( operator === 'min-' ) { 26979 // Add this breakpoint and all larger 26980 size = that._find( name ).width; 26981 26982 for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) { 26983 if ( breakpoints[i].width >= size ) { 26984 add( colIdx, breakpoints[i].name ); 26985 } 26986 } 26987 } 26988 else if ( operator === 'not-' ) { 26989 // Add all but this breakpoint 26990 for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) { 26991 if ( breakpoints[i].name.indexOf( matched ) === -1 ) { 26992 add( colIdx, breakpoints[i].name ); 26993 } 26994 } 26995 } 26996 }; 26997 26998 // Loop over each column and determine if it has a responsive control 26999 // class 27000 columns.each( function ( col, i ) { 27001 var classNames = col.className.split(' '); 27002 var hasClass = false; 27003 27004 // Split the class name up so multiple rules can be applied if needed 27005 for ( var k=0, ken=classNames.length ; k<ken ; k++ ) { 27006 var className = classNames[k].trim(); 27007 27008 if ( className === 'all' || className === 'dtr-all' ) { 27009 // Include in all 27010 hasClass = true; 27011 col.includeIn = $.map( breakpoints, function (a) { 27012 return a.name; 27013 } ); 27014 return; 27015 } 27016 else if ( className === 'none' || className === 'dtr-none' || col.never ) { 27017 // Include in none (default) and no auto 27018 hasClass = true; 27019 return; 27020 } 27021 else if ( className === 'control' || className === 'dtr-control' ) { 27022 // Special column that is only visible, when one of the other 27023 // columns is hidden. This is used for the details control 27024 hasClass = true; 27025 col.control = true; 27026 return; 27027 } 27028 27029 $.each( breakpoints, function ( j, breakpoint ) { 27030 // Does this column have a class that matches this breakpoint? 27031 var brokenPoint = breakpoint.name.split('-'); 27032 var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' ); 27033 var match = className.match( re ); 27034 27035 if ( match ) { 27036 hasClass = true; 27037 27038 if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) { 27039 // Class name matches breakpoint name fully 27040 column( i, breakpoint.name, match[1], match[2]+match[3] ); 27041 } 27042 else if ( match[2] === brokenPoint[0] && ! match[3] ) { 27043 // Class name matched primary breakpoint name with no qualifier 27044 column( i, breakpoint.name, match[1], match[2] ); 27045 } 27046 } 27047 } ); 27048 } 27049 27050 // If there was no control class, then automatic sizing is used 27051 if ( ! hasClass ) { 27052 col.auto = true; 27053 } 27054 } ); 27055 27056 this.s.columns = columns; 27057 }, 27058 27059 /** 27060 * Update the cells to show the correct control class / button 27061 * @private 27062 */ 27063 _controlClass: function () 27064 { 27065 if ( this.c.details.type === 'inline' ) { 27066 var dt = this.s.dt; 27067 var columnsVis = this.s.current; 27068 var firstVisible = $.inArray(true, columnsVis); 27069 27070 // Remove from any cells which shouldn't have it 27071 dt.cells( 27072 null, 27073 function(idx) { 27074 return idx !== firstVisible; 27075 }, 27076 {page: 'current'} 27077 ) 27078 .nodes() 27079 .to$() 27080 .filter('.dtr-control') 27081 .removeClass('dtr-control'); 27082 27083 dt.cells(null, firstVisible, {page: 'current'}) 27084 .nodes() 27085 .to$() 27086 .addClass('dtr-control'); 27087 } 27088 }, 27089 27090 /** 27091 * Show the details for the child row 27092 * 27093 * @param {DataTables.Api} row API instance for the row 27094 * @param {boolean} update Update flag 27095 * @private 27096 */ 27097 _detailsDisplay: function ( row, update ) 27098 { 27099 var that = this; 27100 var dt = this.s.dt; 27101 var details = this.c.details; 27102 27103 if ( details && details.type !== false ) { 27104 var renderer = typeof details.renderer === 'string' 27105 ? Responsive.renderer[details.renderer]() 27106 : details.renderer; 27107 27108 var res = details.display( row, update, function () { 27109 return renderer.call( 27110 that, dt, row[0], that._detailsObj(row[0]) 27111 ); 27112 } ); 27113 27114 if ( res === true || res === false ) { 27115 $(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] ); 27116 } 27117 } 27118 }, 27119 27120 27121 /** 27122 * Initialisation for the details handler 27123 * 27124 * @private 27125 */ 27126 _detailsInit: function () 27127 { 27128 var that = this; 27129 var dt = this.s.dt; 27130 var details = this.c.details; 27131 27132 // The inline type always uses the first child as the target 27133 if ( details.type === 'inline' ) { 27134 details.target = 'td.dtr-control, th.dtr-control'; 27135 } 27136 27137 // Keyboard accessibility 27138 dt.on( 'draw.dtr', function () { 27139 that._tabIndexes(); 27140 } ); 27141 that._tabIndexes(); // Initial draw has already happened 27142 27143 $( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) { 27144 if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) { 27145 $(this).click(); 27146 } 27147 } ); 27148 27149 // type.target can be a string jQuery selector or a column index 27150 var target = details.target; 27151 var selector = typeof target === 'string' ? target : 'td, th'; 27152 27153 if ( target !== undefined || target !== null ) { 27154 // Click handler to show / hide the details rows when they are available 27155 $( dt.table().body() ) 27156 .on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) { 27157 // If the table is not collapsed (i.e. there is no hidden columns) 27158 // then take no action 27159 if ( ! $(dt.table().node()).hasClass('collapsed' ) ) { 27160 return; 27161 } 27162 27163 // Check that the row is actually a DataTable's controlled node 27164 if ( $.inArray( $(this).closest('tr').get(0), dt.rows().nodes().toArray() ) === -1 ) { 27165 return; 27166 } 27167 27168 // For column index, we determine if we should act or not in the 27169 // handler - otherwise it is already okay 27170 if ( typeof target === 'number' ) { 27171 var targetIdx = target < 0 ? 27172 dt.columns().eq(0).length + target : 27173 target; 27174 27175 if ( dt.cell( this ).index().column !== targetIdx ) { 27176 return; 27177 } 27178 } 27179
27180 // $().closest() includes itself in its check 27181 var row = dt.row( $(this).closest('tr') ); 27182 27183 // Check event type to do an action 27184 if ( e.type === 'click' ) { 27185 // The renderer is given as a function so the caller can execute it 27186 // only when they need (i.e. if hiding there is no point is running 27187 // the renderer) 27188 that._detailsDisplay( row, false ); 27189 } 27190 else if ( e.type === 'mousedown' ) { 27191 // For mouse users, prevent the focus ring from showing 27192 $(this).css('outline', 'none'); 27193 } 27194 else if ( e.type === 'mouseup' ) { 27195 // And then re-allow at the end of the click 27196 $(this).trigger('blur').css('outline', ''); 27197 } 27198 } ); 27199 } 27200 }, 27201 27202 27203 /** 27204 * Get the details to pass to a renderer for a row 27205 * @param {int} rowIdx Row index 27206 * @private 27207 */ 27208 _detailsObj: function ( rowIdx ) 27209 { 27210 var that = this; 27211 var dt = this.s.dt; 27212 27213 return $.map( this.s.columns, function( col, i ) { 27214 // Never and control columns should not be passed to the renderer 27215 if ( col.never || col.control ) { 27216 return; 27217 } 27218 27219 var dtCol = dt.settings()[0].aoColumns[ i ]; 27220 27221 return { 27222 className: dtCol.sClass, 27223 columnIndex: i, 27224 data: dt.cell( rowIdx, i ).render( that.c.orthogonal ), 27225 hidden: dt.column( i ).visible() && !that.s.current[ i ], 27226 rowIndex: rowIdx, 27227 title: dtCol.sTitle !== null ? 27228 dtCol.sTitle : 27229 $(dt.column(i).header()).text() 27230 }; 27231 } ); 27232 }, 27233 27234 27235 /** 27236 * Find a breakpoint object from a name 27237 * 27238 * @param {string} name Breakpoint name to find 27239 * @return {object} Breakpoint description object 27240 * @private 27241 */ 27242 _find: function ( name ) 27243 { 27244 var breakpoints = this.c.breakpoints; 27245 27246 for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) { 27247 if ( breakpoints[i].name === name ) { 27248 return breakpoints[i]; 27249 } 27250 } 27251 }, 27252 27253 27254 /** 27255 * Re-create the contents of the child rows as the display has changed in 27256 * some way. 27257 * 27258 * @private 27259 */ 27260 _redrawChildren: function () 27261 { 27262 var that = this; 27263 var dt = this.s.dt; 27264 27265 dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) { 27266 var row = dt.row( idx ); 27267 27268 that._detailsDisplay( dt.row( idx ), true ); 27269 } ); 27270 }, 27271 27272 27273 /** 27274 * Alter the table display for a resized viewport. This involves first 27275 * determining what breakpoint the window currently is in, getting the 27276 * column visibilities to apply and then setting them. 27277 * 27278 * @param {boolean} forceRedraw Force a redraw 27279 * @private 27280 */ 27281 _resize: function (forceRedraw) 27282 { 27283 var that = this; 27284 var dt = this.s.dt; 27285 var width = $(window).innerWidth(); 27286 var breakpoints = this.c.breakpoints; 27287 var breakpoint = breakpoints[0].name; 27288 var columns = this.s.columns; 27289 var i, ien; 27290 var oldVis = this.s.current.slice(); 27291 27292 // Determine what breakpoint we are currently at 27293 for ( i=breakpoints.length-1 ; i>=0 ; i-- ) { 27294 if ( width <= breakpoints[i].width ) { 27295 breakpoint = breakpoints[i].name; 27296 break; 27297 } 27298 } 27299 27300 // Show the columns for that break point 27301 var columnsVis = this._columnsVisiblity( breakpoint ); 27302 this.s.current = columnsVis; 27303 27304 // Set the class before the column visibility is changed so event 27305 // listeners know what the state is. Need to determine if there are 27306 // any columns that are not visible but can be shown 27307 var collapsedClass = false; 27308 27309 for ( i=0, ien=columns.length ; i<ien ; i++ ) { 27310 if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control && ! dt.column(i).visible() === false ) { 27311 collapsedClass = true; 27312 break; 27313 } 27314 } 27315 27316 $( dt.table().node() ).toggleClass( 'collapsed', collapsedClass ); 27317 27318 var changed = false; 27319 var visible = 0; 27320 27321 dt.columns().eq(0).each( function ( colIdx, i ) { 27322 if ( columnsVis[i] === true ) { 27323 visible++; 27324 } 27325 27326 if ( forceRedraw || columnsVis[i] !== oldVis[i] ) { 27327 changed = true; 27328 that._setColumnVis( colIdx, columnsVis[i] ); 27329 } 27330 } ); 27331 27332 // Always need to update the display, regardless of if it has changed or not, so nodes 27333 // can be re-inserted for listHiddenNodes 27334 this._redrawChildren();
27335 27336 if ( changed ) { 27337 // Inform listeners of the change 27338 $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] ); 27339 27340 // If no records, update the "No records" display element 27341 if ( dt.page.info().recordsDisplay === 0 ) { 27342 $('td', dt.table().body()).eq(0).attr('colspan', visible); 27343 } 27344 } 27345 27346 that._controlClass(); 27347 }, 27348 27349 27350 /** 27351 * Determine the width of each column in the table so the auto column hiding 27352 * has that information to work with. This method is never going to be 100% 27353 * perfect since column widths can change slightly per page, but without 27354 * seriously compromising performance this is quite effective. 27355 * 27356 * @private 27357 */ 27358 _resizeAuto: function () 27359 { 27360 var dt = this.s.dt; 27361 var columns = this.s.columns; 27362 var that = this; 27363 27364 // Are we allowed to do auto sizing? 27365 if ( ! this.c.auto ) { 27366 return; 27367 } 27368 27369 // Are there any columns that actually need auto-sizing, or do they all 27370 // have classes defined 27371 if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) { 27372 return; 27373 } 27374 27375 // Need to restore all children. They will be reinstated by a re-render 27376 if ( ! $.isEmptyObject( this.s.childNodeStore ) ) { 27377 $.each( this.s.childNodeStore, function ( key ) { 27378 var idx = key.split('-'); 27379 27380 that._childNodesRestore( dt, idx[0]*1, idx[1]*1 ); 27381 } ); 27382 } 27383 27384 // Clone the table with the current data in it 27385 var tableWidth = dt.table().node().offsetWidth; 27386 var columnWidths = dt.columns; 27387 var clonedTable = dt.table().node().cloneNode( false ); 27388 var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable ); 27389 var clonedBody = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8 27390 27391 clonedTable.style.width = 'auto'; 27392 27393 // Header 27394 var headerCells = dt.columns() 27395 .header() 27396 .filter( function (idx) { 27397 return dt.column(idx).visible(); 27398 } ) 27399 .to$() 27400 .clone( false ) 27401 .css( 'display', 'table-cell' ) 27402 .css( 'width', 'auto' ) 27403 .css( 'min-width', 0 ); 27404 27405 // Body rows - we don't need to take account of DataTables' column 27406 // visibility since we implement our own here (hence the `display` set) 27407 $(clonedBody) 27408 .append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) ) 27409 .find( 'th, td' ).css( 'display', '' ); 27410 27411 // Footer 27412 var footer = dt.table().footer(); 27413 if ( footer ) { 27414 var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable ); 27415 var footerCells = dt.columns() 27416 .footer() 27417 .filter( function (idx) { 27418 return dt.column(idx).visible(); 27419 } ) 27420 .to$() 27421 .clone( false ) 27422 .css( 'display', 'table-cell' ); 27423 27424 $('<tr/>') 27425 .append( footerCells ) 27426 .appendTo( clonedFooter ); 27427 } 27428 27429 $('<tr/>') 27430 .append( headerCells ) 27431 .appendTo( clonedHeader ); 27432 27433 // In the inline case extra padding is applied to the first column to 27434 // give space for the show / hide icon. We need to use this in the 27435 // calculation 27436 if ( this.c.details.type === 'inline' ) { 27437 $(clonedTable).addClass( 'dtr-inline collapsed' ); 27438 } 27439 27440 // It is unsafe to insert elements with the same name into the DOM 27441 // multiple times. For example, cloning and inserting a checked radio 27442 // clears the chcecked state of the original radio. 27443 $( clonedTable ).find( '[name]' ).removeAttr( 'name' ); 27444 27445 // A position absolute table would take the table out of the flow of 27446 // our container element, bypassing the height and width (Scroller) 27447 $( clonedTable ).css( 'position', 'relative' ) 27448 27449 var inserted = $('<div/>') 27450 .css( { 27451 width: 1, 27452 height: 1, 27453 overflow: 'hidden', 27454 clear: 'both' 27455 } ) 27456 .append( clonedTable ); 27457 27458 inserted.insertBefore( dt.table().node() ); 27459 27460 // The cloned header now contains the smallest that each column can be 27461 headerCells.each( function (i) { 27462 var idx = dt.column.index( 'fromVisible', i ); 27463 columns[ idx ].minWidth = this.offsetWidth || 0; 27464 } ); 27465 27466 inserted.remove(); 27467 }, 27468 27469 /** 27470 * Get the state of the current hidden columns - controlled by Responsive only 27471 */ 27472 _responsiveOnlyHidden: function () 27473 { 27474 var dt = this.s.dt; 27475 27476 return $.map( this.s.current, function (v, i) { 27477 // If the column is hidden by DataTables then it can't be hidden by 27478 // Responsive! 27479 if ( dt.column(i).visible() === false ) { 27480 return true; 27481 } 27482 return v; 27483 } ); 27484 }, 27485 27486 /** 27487 * Set a column's visibility. 27488 *
27489 * We don't use DataTables' column visibility controls in order to ensure 27490 * that column visibility can Responsive can no-exist. Since only IE8+ is 27491 * supported (and all evergreen browsers of course) the control of the 27492 * display attribute works well. 27493 * 27494 * @param {integer} col Column index 27495 * @param {boolean} showHide Show or hide (true or false) 27496 * @private 27497 */ 27498 _setColumnVis: function ( col, showHide ) 27499 { 27500 var that = this; 27501 var dt = this.s.dt; 27502 var display = showHide ? '' : 'none'; // empty string will remove the attr 27503 27504 $( dt.column( col ).header() ) 27505 .css( 'display', display ) 27506 .toggleClass('dtr-hidden', !showHide); 27507 27508 $( dt.column( col ).footer() ) 27509 .css( 'display', display ) 27510 .toggleClass('dtr-hidden', !showHide); 27511 27512 dt.column( col ).nodes().to$() 27513 .css( 'display', display ) 27514 .toggleClass('dtr-hidden', !showHide); 27515 27516 // If the are child nodes stored, we might need to reinsert them 27517 if ( ! $.isEmptyObject( this.s.childNodeStore ) ) { 27518 dt.cells( null, col ).indexes().each( function (idx) { 27519 that._childNodesRestore( dt, idx.row, idx.column ); 27520 } ); 27521 } 27522 }, 27523 27524 27525 /** 27526 * Update the cell tab indexes for keyboard accessibility. This is called on 27527 * every table draw - that is potentially inefficient, but also the least 27528 * complex option given that column visibility can change on the fly. Its a 27529 * shame user-focus was removed from CSS 3 UI, as it would have solved this 27530 * issue with a single CSS statement. 27531 * 27532 * @private 27533 */ 27534 _tabIndexes: function () 27535 { 27536 var dt = this.s.dt; 27537 var cells = dt.cells( { page: 'current' } ).nodes().to$(); 27538 var ctx = dt.settings()[0]; 27539 var target = this.c.details.target; 27540 27541 cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' ); 27542 27543 if ( typeof target === 'number' ) { 27544 dt.cells( null, target, { page: 'current' } ).nodes().to$() 27545 .attr( 'tabIndex', ctx.iTabIndex ) 27546 .data( 'dtr-keyboard', 1 ); 27547 } 27548 else { 27549 // This is a bit of a hack - we need to limit the selected nodes to just 27550 // those of this table 27551 if ( target === 'td:first-child, th:first-child' ) { 27552 target = '>td:first-child, >th:first-child'; 27553 } 27554 27555 $( target, dt.rows( { page: 'current' } ).nodes() ) 27556 .attr( 'tabIndex', ctx.iTabIndex ) 27557 .data( 'dtr-keyboard', 1 ); 27558 } 27559 } 27560} ); 27561 27562 27563/** 27564 * List of default breakpoints. Each item in the array is an object with two 27565 * properties: 27566 * 27567 * * `name` - the breakpoint name. 27568 * * `width` - the breakpoint width 27569 * 27570 * @name Responsive.breakpoints 27571 * @static 27572 */ 27573Responsive.breakpoints = [ 27574 { name: 'desktop', width: Infinity }, 27575 { name: 'tablet-l', width: 1024 }, 27576 { name: 'tablet-p', width: 768 }, 27577 { name: 'mobile-l', width: 480 }, 27578 { name: 'mobile-p', width: 320 } 27579]; 27580 27581 27582/** 27583 * Display methods - functions which define how the hidden data should be shown 27584 * in the table. 27585 * 27586 * @namespace 27587 * @name Responsive.defaults 27588 * @static 27589 */ 27590Responsive.display = { 27591 childRow: function ( row, update, render ) { 27592 if ( update ) { 27593 if ( $(row.node()).hasClass('parent') ) { 27594 row.child( render(), 'child' ).show(); 27595 27596 return true; 27597 } 27598 } 27599 else { 27600 if ( ! row.child.isShown() ) { 27601 row.child( render(), 'child' ).show(); 27602 $( row.node() ).addClass( 'parent' ); 27603 27604 return true; 27605 } 27606 else { 27607 row.child( false ); 27608 $( row.node() ).removeClass( 'parent' ); 27609 27610 return false; 27611 } 27612 } 27613 }, 27614 27615 childRowImmediate: function ( row, update, render ) { 27616 if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) { 27617 // User interaction and the row is show, or nothing to show 27618 row.child( false ); 27619 $( row.node() ).removeClass( 'parent' ); 27620 27621 return false; 27622 } 27623 else { 27624 // Display 27625 row.child( render(), 'child' ).show(); 27626 $( row.node() ).addClass( 'parent' ); 27627 27628 return true; 27629 } 27630 }, 27631 27632 // This is a wrapper so the modal options for Bootstrap and jQuery UI can 27633 // have options passed into them. This specific one doesn't need to be a 27634 // function but it is for consistency in the `modal` name 27635 modal: function ( options ) { 27636 return function ( row, update, render ) { 27637 if ( ! update ) { 27638 // Show a modal 27639 var close = function () { 27640 modal.remove(); // will tidy events for us 27641 $(document).off( 'keypress.dtr' ); 27642 }; 27643
27644 var modal = $('<div class="dtr-modal"/>') 27645 .append( $('<div class="dtr-modal-display"/>') 27646 .append( $('<div class="dtr-modal-content"/>') 27647 .append( render() ) 27648 ) 27649 .append( $('<div class="dtr-modal-close">×</div>' ) 27650 .click( function () { 27651 close(); 27652 } ) 27653 ) 27654 ) 27655 .append( $('<div class="dtr-modal-background"/>') 27656 .click( function () { 27657 close(); 27658 } ) 27659 ) 27660 .appendTo( 'body' ); 27661 27662 $(document).on( 'keyup.dtr', function (e) { 27663 if ( e.keyCode === 27 ) { 27664 e.stopPropagation(); 27665 27666 close(); 27667 } 27668 } ); 27669 } 27670 else { 27671 $('div.dtr-modal-content') 27672 .empty() 27673 .append( render() ); 27674 } 27675 27676 if ( options && options.header ) { 27677 $('div.dtr-modal-content').prepend( 27678 '<h2>'+options.header( row )+'</h2>' 27679 ); 27680 } 27681 }; 27682 } 27683}; 27684 27685 27686/** 27687 * Display methods - functions which define how the hidden data should be shown 27688 * in the table. 27689 * 27690 * @namespace 27691 * @name Responsive.defaults 27692 * @static 27693 */ 27694Responsive.renderer = { 27695 listHiddenNodes: function () { 27696 return function ( api, rowIdx, columns ) { 27697 var that = this; 27698 var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>'); 27699 var found = false; 27700 27701 var data = $.each( columns, function ( i, col ) { 27702 if ( col.hidden ) { 27703 var klass = col.className ? 27704 'class="'+ col.className +'"' : 27705 ''; 27706 27707 $( 27708 '<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+ 27709 '<span class="dtr-title">'+ 27710 col.title+ 27711 '</span> '+ 27712 '</li>' 27713 ) 27714 .append( $('<span class="dtr-data"/>').append( that._childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) ) 27715 .appendTo( ul ); 27716 27717 found = true; 27718 } 27719 } ); 27720 27721 return found ? 27722 ul : 27723 false; 27724 }; 27725 }, 27726 27727 listHidden: function () { 27728 return function ( api, rowIdx, columns ) { 27729 var data = $.map( columns, function ( col ) { 27730 var klass = col.className ? 27731 'class="'+ col.className +'"' : 27732 ''; 27733 27734 return col.hidden ? 27735 '<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+ 27736 '<span class="dtr-title">'+ 27737 col.title+ 27738 '</span> '+ 27739 '<span class="dtr-data">'+ 27740 col.data+ 27741 '</span>'+ 27742 '</li>' : 27743 ''; 27744 } ).join(''); 27745 27746 return data ? 27747 $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>').append( data ) : 27748 false; 27749 } 27750 }, 27751 27752 tableAll: function ( options ) { 27753 options = $.extend( { 27754 tableClass: '' 27755 }, options ); 27756 27757 return function ( api, rowIdx, columns ) { 27758 var data = $.map( columns, function ( col ) { 27759 var klass = col.className ? 27760 'class="'+ col.className +'"' : 27761 ''; 27762 27763 return '<tr '+klass+' data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+ 27764 '<td>'+col.title+':'+'</td> '+ 27765 '<td>'+col.data+'</td>'+ 27766 '</tr>'; 27767 } ).join(''); 27768 27769 return $('<table class="'+options.tableClass+' dtr-details" width="100%"/>').append( data ); 27770 } 27771 } 27772}; 27773 27774/** 27775 * Responsive default settings for initialisation 27776 * 27777 * @namespace 27778 * @name Responsive.defaults 27779 * @static 27780 */ 27781Responsive.defaults = { 27782 /** 27783 * List of breakpoints for the instance. Note that this means that each 27784 * instance can have its own breakpoints. Additionally, the breakpoints 27785 * cannot be changed once an instance has been creased. 27786 * 27787 * @type {Array} 27788 * @default Takes the value of `Responsive.breakpoints` 27789 */ 27790 breakpoints: Responsive.breakpoints, 27791 27792 /** 27793 * Enable / disable auto hiding calculations. It can help to increase 27794 * performance slightly if you disable this option, but all columns would 27795 * need to have breakpoint classes assigned to them 27796 * 27797 * @type {Boolean} 27798 * @default `true` 27799 */ 27800 auto: true, 27801 27802 /** 27803 * Details control. If given as a string value, the `type` property of the 27804 * default object is set to that value, and the defaults used for the rest 27805 * of the object - this is for ease of implementation. 27806 *
27807 * The object consists of the following properties: 27808 * 27809 * * `display` - A function that is used to show and hide the hidden details 27810 * * `renderer` - function that is called for display of the child row data. 27811 * The default function will show the data from the hidden columns 27812 * * `target` - Used as the selector for what objects to attach the child 27813 * open / close to 27814 * * `type` - `false` to disable the details display, `inline` or `column` 27815 * for the two control types 27816 * 27817 * @type {Object|string} 27818 */ 27819 details: { 27820 display: Responsive.display.childRow, 27821 27822 renderer: Responsive.renderer.listHidden(), 27823 27824 target: 0, 27825 27826 type: 'inline' 27827 }, 27828 27829 /** 27830 * Orthogonal data request option. This is used to define the data type 27831 * requested when Responsive gets the data to show in the child row. 27832 * 27833 * @type {String} 27834 */ 27835 orthogonal: 'display' 27836}; 27837 27838 27839/* 27840 * API 27841 */ 27842var Api = $.fn.dataTable.Api; 27843 27844// Doesn't do anything - work around for a bug in DT... Not documented 27845Api.register( 'responsive()', function () { 27846 return this; 27847} ); 27848 27849Api.register( 'responsive.index()', function ( li ) { 27850 li = $(li); 27851 27852 return { 27853 column: li.data('dtr-index'), 27854 row: li.parent().data('dtr-index') 27855 }; 27856} ); 27857 27858Api.register( 'responsive.rebuild()', function () { 27859 return this.iterator( 'table', function ( ctx ) { 27860 if ( ctx._responsive ) { 27861 ctx._responsive._classLogic(); 27862 } 27863 } ); 27864} ); 27865 27866Api.register( 'responsive.recalc()', function () { 27867 return this.iterator( 'table', function ( ctx ) { 27868 if ( ctx._responsive ) { 27869 ctx._responsive._resizeAuto(); 27870 ctx._responsive._resize(); 27871 } 27872 } ); 27873} ); 27874 27875Api.register( 'responsive.hasHidden()', function () { 27876 var ctx = this.context[0]; 27877 27878 return ctx._responsive ? 27879 $.inArray( false, ctx._responsive._responsiveOnlyHidden() ) !== -1 : 27880 false; 27881} ); 27882 27883Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()', function () { 27884 return this.iterator( 'column', function ( settings, column ) { 27885 return settings._responsive ?
27886 settings._responsive._responsiveOnlyHidden()[ column ] : 27887 false; 27888 }, 1 ); 27889} ); 27890 27891 27892/** 27893 * Version information 27894 * 27895 * @name Responsive.version 27896 * @static 27897 */ 27898Responsive.version = '2.4.1'; 27899 27900 27901$.fn.dataTable.Responsive = Responsive; 27902$.fn.DataTable.Responsive = Responsive; 27903 27904// Attach a listener to the document which listens for DataTables initialisation 27905// events so we can automatically initialise 27906$(document).on( 'preInit.dt.dtr', function (e, settings, json) { 27907 if ( e.namespace !== 'dt' ) { 27908 return; 27909 } 27910 27911 if ( $(settings.nTable).hasClass( 'responsive' ) || 27912 $(settings.nTable).hasClass( 'dt-responsive' ) || 27913 settings.oInit.responsive || 27914 DataTable.defaults.responsive 27915 ) { 27916 var init = settings.oInit.responsive; 27917 27918 if ( init !== false ) { 27919 new Responsive( settings, $.isPlainObject( init ) ? init : {} ); 27920 } 27921 } 27922} ); 27923 27924 27925return DataTable; 27926})); 27927 27928 27929/*! DataTables styling wrapper for Responsive 27930 * © SpryMedia Ltd - datatables.net/license 27931 */ 27932 27933(function( factory ){ 27934 if ( typeof define === 'function' && define.amd ) { 27935 // AMD 27936 define( ['jquery', 'datatables.net-dt', 'datatables.net-responsive'], function ( $ ) { 27937 return factory( $, window, document ); 27938 } ); 27939 } 27940 else if ( typeof exports === 'object' ) { 27941 // CommonJS 27942 var jq = require('jquery'); 27943 var cjsRequires = function (root, $) { 27944 if ( ! $.fn.dataTable ) { 27945 require('datatables.net-dt')(root, $); 27946 } 27947 27948 if ( ! $.fn.dataTable.Responsive ) { 27949 require('datatables.net-responsive')(root, $); 27950 } 27951 }; 27952 27953 if (typeof window !== 'undefined') { 27954 module.exports = function (root, $) { 27955 if ( ! root ) { 27956 // CommonJS environments without a window global must pass a 27957 // root. This will give an error otherwise 27958 root = window; 27959 } 27960 27961 if ( ! $ ) { 27962 $ = jq( root ); 27963 } 27964 27965 cjsRequires( root, $ ); 27966 return factory( $, root, root.document ); 27967 }; 27968 } 27969 else { 27970 cjsRequires( window, jq ); 27971 module.exports = factory( jq, window, window.document ); 27972 } 27973 } 27974 else { 27975 // Browser 27976 factory( jQuery, window, document ); 27977 } 27978}(function( $, window, document, undefined ) { 27979'use strict'; 27980var DataTable = $.fn.dataTable; 27981 27982 27983 27984 27985return DataTable; 27986})); 27987 27988 27989/*! RowGroup 1.3.1 27990 * © SpryMedia Ltd - datatables.net/license 27991 */ 27992 27993(function( factory ){ 27994 if ( typeof define === 'function' && define.amd ) { 27995 // AMD 27996 define( ['jquery', 'datatables.net'], function ( $ ) { 27997 return factory( $, window, document ); 27998 } ); 27999 } 28000 else if ( typeof exports === 'object' ) { 28001 // CommonJS 28002 var jq = require('jquery'); 28003 var cjsRequires = function (root, $) { 28004 if ( ! $.fn.dataTable ) { 28005 require('datatables.net')(root, $); 28006 } 28007 }; 28008 28009 if (typeof window !== 'undefined') { 28010 module.exports = function (root, $) { 28011 if ( ! root ) { 28012 // CommonJS environments without a window global must pass a 28013 // root. This will give an error otherwise 28014 root = window; 28015 } 28016 28017 if ( ! $ ) { 28018 $ = jq( root ); 28019 } 28020 28021 cjsRequires( root, $ ); 28022 return factory( $, root, root.document ); 28023 }; 28024 } 28025 else { 28026 cjsRequires( window, jq ); 28027 module.exports = factory( jq, window, window.document ); 28028 } 28029 } 28030 else { 28031 // Browser 28032 factory( jQuery, window, document ); 28033 } 28034}(function( $, window, document, undefined ) { 28035'use strict'; 28036var DataTable = $.fn.dataTable; 28037 28038 28039 28040/** 28041 * @summary RowGroup 28042 * @description RowGrouping for DataTables 28043 * @version 1.3.1 28044 * @author SpryMedia Ltd (www.sprymedia.co.uk) 28045 * @contact datatables.net 28046 * @copyright SpryMedia Ltd. 28047 * 28048 * This source file is free software, available under the following license: 28049 * MIT license - http://datatables.net/license/mit 28050 * 28051 * This source file is distributed in the hope that it will be useful, but 28052 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 28053 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 28054 * 28055 * For details please refer to: http://www.datatables.net 28056 */ 28057 28058var RowGroup = function ( dt, opts ) { 28059 // Sanity check that we are using DataTables 1.10 or newer
28060 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { 28061 throw 'RowGroup requires DataTables 1.10.8 or newer'; 28062 } 28063 28064 // User and defaults configuration object 28065 this.c = $.extend( true, {}, 28066 DataTable.defaults.rowGroup, 28067 RowGroup.defaults, 28068 opts 28069 ); 28070 28071 // Internal settings 28072 this.s = { 28073 dt: new DataTable.Api( dt ) 28074 }; 28075 28076 // DOM items 28077 this.dom = { 28078 28079 }; 28080 28081 // Check if row grouping has already been initialised on this table 28082 var settings = this.s.dt.settings()[0]; 28083 var existing = settings.rowGroup; 28084 if ( existing ) { 28085 return existing; 28086 } 28087 28088 settings.rowGroup = this; 28089 this._constructor(); 28090}; 28091 28092 28093$.extend( RowGroup.prototype, { 28094 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 28095 * API methods for DataTables API interface 28096 */ 28097 28098 /** 28099 * Get/set the grouping data source - need to call draw after this is 28100 * executed as a setter 28101 * @returns string~RowGroup 28102 */ 28103 dataSrc: function ( val ) 28104 { 28105 if ( val === undefined ) { 28106 return this.c.dataSrc; 28107 } 28108 28109 var dt = this.s.dt; 28110 28111 this.c.dataSrc = val; 28112 28113 $(dt.table().node()).triggerHandler( 'rowgroup-datasrc.dt', [ dt, val ] ); 28114 28115 return this; 28116 }, 28117 28118 /** 28119 * Disable - need to call draw after this is executed 28120 * @returns RowGroup 28121 */ 28122 disable: function () 28123 { 28124 this.c.enable = false; 28125 return this; 28126 }, 28127 28128 /** 28129 * Enable - need to call draw after this is executed 28130 * @returns RowGroup 28131 */ 28132 enable: function ( flag ) 28133 { 28134 if ( flag === false ) { 28135 return this.disable(); 28136 } 28137 28138 this.c.enable = true; 28139 return this; 28140 }, 28141 28142 /** 28143 * Get enabled flag 28144 * @returns boolean 28145 */ 28146 enabled: function () 28147 { 28148 return this.c.enable; 28149 }, 28150 28151 28152 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 28153 * Constructor 28154 */ 28155 _constructor: function () 28156 { 28157 var that = this; 28158 var dt = this.s.dt; 28159 var hostSettings = dt.settings()[0]; 28160 28161 dt.on( 'draw.dtrg', function (e, s) { 28162 if ( that.c.enable && hostSettings === s ) { 28163 that._draw(); 28164 } 28165 } ); 28166 28167 dt.on( 'column-visibility.dt.dtrg responsive-resize.dt.dtrg', function () { 28168 that._adjustColspan(); 28169 } ); 28170 28171 dt.on( 'destroy', function () { 28172 dt.off( '.dtrg' ); 28173 } ); 28174 }, 28175 28176 28177 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 28178 * Private methods 28179 */ 28180 28181 /** 28182 * Adjust column span when column visibility changes 28183 * @private 28184 */ 28185 _adjustColspan: function () 28186 { 28187 $( 'tr.'+this.c.className, this.s.dt.table().body() ).find('td:visible') 28188 .attr( 'colspan', this._colspan() ); 28189 }, 28190 28191 /** 28192 * Get the number of columns that a grouping row should span 28193 * @private 28194 */ 28195 _colspan: function () 28196 { 28197 return this.s.dt.columns().visible().reduce( function (a, b) { 28198 return a + b; 28199 }, 0 ); 28200 }, 28201 28202 28203 /** 28204 * Update function that is called whenever we need to draw the grouping rows. 28205 * This is basically a bootstrap for the self iterative _group and _groupDisplay 28206 * methods 28207 * @private 28208 */ 28209 _draw: function () 28210 { 28211 var dt = this.s.dt; 28212 var groupedRows = this._group( 0, dt.rows( { page: 'current' } ).indexes() ); 28213 28214 this._groupDisplay( 0, groupedRows ); 28215 }, 28216 28217 /** 28218 * Get the grouping information from a data set (index) of rows 28219 * @param {number} level Nesting level 28220 * @param {DataTables.Api} rows API of the rows to consider for this group 28221 * @returns {object[]} Nested grouping information - it is structured like this: 28222 * { 28223 * dataPoint: 'Edinburgh', 28224 * rows: [ 1,2,3,4,5,6,7 ], 28225 * children: [ { 28226 * dataPoint: 'developer' 28227 * rows: [ 1, 2, 3 ] 28228 * }, 28229 * { 28230 * dataPoint: 'support', 28231 * rows: [ 4, 5, 6, 7 ] 28232 * } ] 28233 * } 28234 * @private 28235 */ 28236 _group: function ( level, rows ) { 28237 var fns = Array.isArray( this.c.dataSrc ) ? this.c.dataSrc : [ this.c.dataSrc ]; 28238 var fn = DataTable.ext.oApi._fnGetObjectDataFn( fns[ level ] ); 28239 var dt = this.s.dt; 28240 var group, last; 28241 var data = []; 28242 var that = this; 28243 28244 for ( var i=0, ien=rows.length ; i<ien ; i++ ) { 28245 var rowIndex = rows[i]; 28246 var rowData = dt.row( rowIndex ).data(); 28247 var group = fn( rowData ); 28248 28249 if ( group === null || group === undefined ) { 28250 group = that.c.emptyDataGroup; 28251 } 28252 28253 if ( last === undefined || group !== last ) { 28254 data.push( { 28255 dataPoint: group, 28256 rows: [] 28257 } ); 28258 28259 last = group; 28260 } 28261 28262 data[ data.length-1 ].rows.push( rowIndex ); 28263 } 28264 28265 if ( fns[ level+1 ] !== undefined ) { 28266 for ( var i=0, ien=data.length ; i<ien ; i++ ) {
28267 data[i].children = this._group( level+1, data[i].rows ); 28268 } 28269 } 28270 28271 return data; 28272 }, 28273 28274 /** 28275 * Row group display - insert the rows into the document 28276 * @param {number} level Nesting level 28277 * @param {object[]} groups Takes the nested array from `_group` 28278 * @private 28279 */ 28280 _groupDisplay: function ( level, groups ) 28281 { 28282 var dt = this.s.dt; 28283 var display; 28284 28285 for ( var i=0, ien=groups.length ; i<ien ; i++ ) { 28286 var group = groups[i]; 28287 var groupName = group.dataPoint; 28288 var row; 28289 var rows = group.rows; 28290 28291 if ( this.c.startRender ) { 28292 display = this.c.startRender.call( this, dt.rows(rows), groupName, level ); 28293 row = this._rowWrap( display, this.c.startClassName, level ); 28294 28295 if ( row ) { 28296 row.insertBefore( dt.row( rows[0] ).node() ); 28297 } 28298 } 28299 28300 if ( this.c.endRender ) { 28301 display = this.c.endRender.call( this, dt.rows(rows), groupName, level ); 28302 row = this._rowWrap( display, this.c.endClassName, level ); 28303 28304 if ( row ) { 28305 row.insertAfter( dt.row( rows[ rows.length-1 ] ).node() ); 28306 } 28307 } 28308 28309 if ( group.children ) { 28310 this._groupDisplay( level+1, group.children ); 28311 } 28312 } 28313 }, 28314 28315 /** 28316 * Take a rendered value from an end user and make it suitable for display 28317 * as a row, by wrapping it in a row, or detecting that it is a row. 28318 * @param {node|jQuery|string} display Display value 28319 * @param {string} className Class to add to the row 28320 * @param {array} group 28321 * @param {number} group level 28322 * @private 28323 */ 28324 _rowWrap: function ( display, className, level ) 28325 { 28326 var row; 28327 28328 if ( display === null || display === '' ) { 28329 display = this.c.emptyDataGroup; 28330 } 28331 28332 if ( display === undefined || display === null ) { 28333 return null; 28334 } 28335 28336 if ( typeof display === 'object' && display.nodeName && display.nodeName.toLowerCase() === 'tr') { 28337 row = $(display); 28338 } 28339 else if (display instanceof $ && display.length && display[0].nodeName.toLowerCase() === 'tr') { 28340 row = display; 28341 } 28342 else { 28343 row = $('<tr/>') 28344 .append( 28345 $('<th/>') 28346 .attr( 'colspan', this._colspan() ) 28347 .attr( 'scope', 'row' ) 28348 .append( display ) 28349 ); 28350 } 28351 28352 return row 28353 .addClass( this.c.className ) 28354 .addClass( className ) 28355 .addClass( 'dtrg-level-'+level ); 28356 } 28357} ); 28358 28359 28360/** 28361 * RowGroup default settings for initialisation 28362 * 28363 * @namespace 28364 * @name RowGroup.defaults 28365 * @static 28366 */ 28367RowGroup.defaults = { 28368 /** 28369 * Class to apply to grouping rows - applied to both the start and 28370 * end grouping rows. 28371 * @type string 28372 */ 28373 className: 'dtrg-group', 28374 28375 /** 28376 * Data property from which to read the grouping information 28377 * @type string|integer|array 28378 */ 28379 dataSrc: 0, 28380 28381 /** 28382 * Text to show if no data is found for a group 28383 * @type string 28384 */ 28385 emptyDataGroup: 'No group', 28386 28387 /** 28388 * Initial enablement state 28389 * @boolean 28390 */ 28391 enable: true, 28392 28393 /** 28394 * Class name to give to the end grouping row 28395 * @type string 28396 */ 28397 endClassName: 'dtrg-end', 28398 28399 /** 28400 * End grouping label function 28401 * @function 28402 */ 28403 endRender: null, 28404 28405 /** 28406 * Class name to give to the start grouping row 28407 * @type string 28408 */ 28409 startClassName: 'dtrg-start', 28410 28411 /** 28412 * Start grouping label function 28413 * @function 28414 */ 28415 startRender: function ( rows, group ) { 28416 return group; 28417 } 28418}; 28419 28420 28421RowGroup.version = "1.3.1"; 28422 28423 28424$.fn.dataTable.RowGroup = RowGroup; 28425$.fn.DataTable.RowGroup = RowGroup; 28426 28427 28428DataTable.Api.register( 'rowGroup()', function () { 28429 return this; 28430} ); 28431 28432DataTable.Api.register( 'rowGroup().disable()', function () { 28433 return this.iterator( 'table', function (ctx) { 28434 if ( ctx.rowGroup ) { 28435 ctx.rowGroup.enable( false ); 28436 } 28437 } ); 28438} ); 28439 28440DataTable.Api.register( 'rowGroup().enable()', function ( opts ) { 28441 return this.iterator( 'table', function (ctx) { 28442 if ( ctx.rowGroup ) { 28443 ctx.rowGroup.enable( opts === undefined ? true : opts ); 28444 } 28445 } ); 28446} ); 28447 28448DataTable.Api.register( 'rowGroup().enabled()', function () { 28449 var ctx = this.context; 28450 28451 return ctx.length && ctx[0].rowGroup ? 28452 ctx[0].rowGroup.enabled() : 28453 false; 28454} ); 28455
28456DataTable.Api.register( 'rowGroup().dataSrc()', function ( val ) { 28457 if ( val === undefined ) { 28458 return this.context[0].rowGroup.dataSrc(); 28459 } 28460 28461 return this.iterator( 'table', function (ctx) { 28462 if ( ctx.rowGroup ) { 28463 ctx.rowGroup.dataSrc( val ); 28464 } 28465 } ); 28466} ); 28467 28468 28469// Attach a listener to the document which listens for DataTables initialisation 28470// events so we can automatically initialise 28471$(document).on( 'preInit.dt.dtrg', function (e, settings, json) { 28472 if ( e.namespace !== 'dt' ) { 28473 return; 28474 } 28475 28476 var init = settings.oInit.rowGroup; 28477 var defaults = DataTable.defaults.rowGroup; 28478 28479 if ( init || defaults ) { 28480 var opts = $.extend( {}, defaults, init ); 28481 28482 if ( init !== false ) { 28483 new RowGroup( settings, opts ); 28484 } 28485 } 28486} ); 28487 28488 28489return DataTable; 28490})); 28491 28492 28493/*! RowReorder 1.3.3 28494 * © SpryMedia Ltd - datatables.net/license 28495 */ 28496 28497(function( factory ){ 28498 if ( typeof define === 'function' && define.amd ) { 28499 // AMD 28500 define( ['jquery', 'datatables.net'], function ( $ ) { 28501 return factory( $, window, document ); 28502 } ); 28503 } 28504 else if ( typeof exports === 'object' ) { 28505 // CommonJS 28506 var jq = require('jquery'); 28507 var cjsRequires = function (root, $) { 28508 if ( ! $.fn.dataTable ) { 28509 require('datatables.net')(root, $); 28510 } 28511 }; 28512 28513 if (typeof window !== 'undefined') { 28514 module.exports = function (root, $) { 28515 if ( ! root ) { 28516 // CommonJS environments without a window global must pass a 28517 // root. This will give an error otherwise 28518 root = window; 28519 } 28520 28521 if ( ! $ ) { 28522 $ = jq( root ); 28523 } 28524 28525 cjsRequires( root, $ ); 28526 return factory( $, root, root.document ); 28527 }; 28528 } 28529 else { 28530 cjsRequires( window, jq ); 28531 module.exports = factory( jq, window, window.document ); 28532 } 28533 } 28534 else { 28535 // Browser 28536 factory( jQuery, window, document ); 28537 } 28538}(function( $, window, document, undefined ) { 28539'use strict'; 28540var DataTable = $.fn.dataTable; 28541 28542 28543 28544/** 28545 * @summary RowReorder 28546 * @description Row reordering extension for DataTables 28547 * @version 1.3.3 28548 * @file dataTables.rowReorder.js 28549 * @author SpryMedia Ltd 28550 * @contact datatables.net 28551 * @copyright Copyright 2015-2023 SpryMedia Ltd. 28552 * 28553 * This source file is free software, available under the following license: 28554 * MIT license - http://datatables.net/license/mit 28555 * 28556 * This source file is distributed in the hope that it will be useful, but 28557 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 28558 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 28559 * 28560 * For details please refer to: http://www.datatables.net 28561 */ 28562 28563/** 28564 * RowReorder provides the ability in DataTables to click and drag rows to 28565 * reorder them. When a row is dropped the data for the rows effected will be 28566 * updated to reflect the change. Normally this data point should also be the 28567 * column being sorted upon in the DataTable but this does not need to be the 28568 * case. RowReorder implements a "data swap" method - so the rows being 28569 * reordered take the value of the data point from the row that used to occupy 28570 * the row's new position. 28571 * 28572 * Initialisation is done by either: 28573 * 28574 * * `rowReorder` parameter in the DataTable initialisation object 28575 * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables 28576 * initialisation. 28577 * 28578 * @class 28579 * @param {object} settings DataTables settings object for the host table 28580 * @param {object} [opts] Configuration options 28581 * @requires jQuery 1.7+ 28582 * @requires DataTables 1.10.7+ 28583 */ 28584var RowReorder = function ( dt, opts ) { 28585 // Sanity check that we are using DataTables 1.10 or newer 28586 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { 28587 throw 'DataTables RowReorder requires DataTables 1.10.8 or newer'; 28588 } 28589 28590 // User and defaults configuration object 28591 this.c = $.extend( true, {}, 28592 DataTable.defaults.rowReorder, 28593 RowReorder.defaults, 28594 opts 28595 ); 28596 28597 // Internal settings 28598 this.s = { 28599 /** @type {integer}
28599 Scroll body top cache */ 28600 bodyTop: null, 28601 28602 /** @type {DataTable.Api} DataTables' API instance */ 28603 dt: new DataTable.Api( dt ), 28604 28605 /** @type {function} Data fetch function */ 28606 getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ), 28607 28608 /** @type {array} Pixel positions for row insertion calculation */ 28609 middles: null, 28610 28611 /** @type {Object} Cached dimension information for use in the mouse move event handler */ 28612 scroll: {}, 28613 28614 /** @type {integer} Interval object used for smooth scrolling */ 28615 scrollInterval: null, 28616 28617 /** @type {function} Data set function */ 28618 setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ), 28619 28620 /** @type {Object} Mouse down information */ 28621 start: { 28622 top: 0, 28623 left: 0, 28624 offsetTop: 0, 28625 offsetLeft: 0, 28626 nodes: [] 28627 }, 28628 28629 /** @type {integer} Window height cached value */ 28630 windowHeight: 0, 28631 28632 /** @type {integer} Document outer height cached value */ 28633 documentOuterHeight: 0, 28634 28635 /** @type {integer} DOM clone outer height cached value */ 28636 domCloneOuterHeight: 0 28637 }; 28638 28639 // DOM items 28640 this.dom = { 28641 /** @type {jQuery} Cloned row being moved around */ 28642 clone: null, 28643 cloneParent: null, 28644 28645 /** @type {jQuery} DataTables scrolling container */ 28646 dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container()) 28647 }; 28648 28649 // Check if row reorder has already been initialised on this table 28650 var settings = this.s.dt.settings()[0]; 28651 var exisiting = settings.rowreorder; 28652 28653 if ( exisiting ) { 28654 return exisiting; 28655 } 28656 28657 if ( !this.dom.dtScroll.length ) { 28658 this.dom.dtScroll = $(this.s.dt.table().container(), 'tbody') 28659 } 28660 28661 settings.rowreorder = this; 28662 this._constructor(); 28663}; 28664 28665 28666$.extend( RowReorder.prototype, { 28667 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 28668 * Constructor 28669 */ 28670 28671 /** 28672 * Initialise the RowReorder instance 28673 * 28674 * @private 28675 */ 28676 _constructor: function () 28677 { 28678 var that = this; 28679 var dt = this.s.dt; 28680 var table = $( dt.table().node() ); 28681 28682 // Need to be able to calculate the row positions relative to the table 28683 if ( table.css('position') === 'static' ) { 28684 table.css( 'position', 'relative' ); 28685 } 28686 28687 // listen for mouse down on the target column - we have to implement 28688 // this rather than using HTML5 drag and drop as drag and drop doesn't 28689 // appear to work on table rows at this time. Also mobile browsers are 28690 // not supported. 28691 // Use `table().container()` rather than just the table node for IE8 - 28692 // otherwise it only works once... 28693 $(dt.table().container()).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) { 28694 if ( ! that.c.enable ) { 28695 return; 28696 } 28697 28698 // Ignore excluded children of the selector 28699 if ( $(e.target).is(that.c.excludedChildren) ) { 28700 return true; 28701 } 28702 28703 var tr = $(this).closest('tr'); 28704 var row = dt.row( tr ); 28705 28706 // Double check that it is a DataTable row 28707 if ( row.any() ) { 28708 that._emitEvent( 'pre-row-reorder', { 28709 node: row.node(), 28710 index: row.index() 28711 } ); 28712 28713 that._mouseDown( e, tr ); 28714 return false; 28715 } 28716 } ); 28717 28718 dt.on( 'destroy.rowReorder', function () { 28719 $(dt.table().container()).off( '.rowReorder' ); 28720 dt.off( '.rowReorder' ); 28721 } ); 28722 }, 28723 28724 28725 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 28726 * Private methods 28727 */ 28728 28729 /** 28730 * Cache the measurements that RowReorder needs in the mouse move handler 28731 * to attempt to speed things up, rather than reading from the DOM. 28732 * 28733 * @private 28734 */ 28735 _cachePositions: function () 28736 { 28737 var dt = this.s.dt; 28738 28739 // Frustratingly, if we add `position:relative` to the tbody, the 28740 // position is still relatively to the parent. So we need to adjust 28741 // for that 28742 var headerHeight = $( dt.table().node() ).find('thead').outerHeight(); 28743 28744 // Need to pass the nodes through jQuery to get them in document order, 28745 // not what DataTables thinks it is, since we have been altering the 28746 // order 28747 var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); 28748 var middles = $.map( nodes, function ( node, i ) { 28749 var top = $(node).position().top - headerHeight; 28750 28751 return (top + top + $(node).outerHeight() ) / 2; 28752 } ); 28753 28754 this.s.middles = middles; 28755 this.s.bodyTop = $( dt.table().body() ).offset().top; 28756 this.s.windowHeight = $(window).height(); 28757 this.s.documentOuterHeight = $(document).outerHeight(); 28758 }, 28759 28760 28761 /** 28762 * Clone a row so it can be floated around the screen 28763 * 28764 * @param {jQuery} target Node to be cloned 28765 * @private 28766 */ 28767 _clone: function ( target ) 28768 { 28769 var dt = this.s.dt; 28770 var clone = $( dt.table().node().cloneNode(false) ) 28771 .addClass( 'dt-rowReorder-float' ) 28772 .append('<tbody/>') 28773 .append( target.clone( false ) ); 28774 28775 // Match the table and column widths - read all sizes before setting 28776 // to reduce reflows 28777 var tableWidth = target.outerWidth(); 28778 var tableHeight = target.outerHeight(); 28779 var scrollBody = $($(this.s.dt.table().node()).parent()); 28780 var scrollWidth = scrollBody.width(); 28781 var scrollLeft = scrollBody.scrollLeft(); 28782 var sizes = target.children().map( function () { 28783 return $(this).width(); 28784 } ); 28785 28786 clone 28787 .width( tableWidth ) 28788 .height( tableHeight ) 28789 .find('tr').children().each( function (i) { 28790 this.style.width = sizes[i]+'px'; 28791 } ); 28792 28793 var cloneParent = $('<div>') 28794 .addClass('dt-rowReorder-float-parent') 28795 .width(scrollWidth) 28796 .append(clone) 28797 .appendTo( 'body' ) 28798 .scrollLeft(scrollLeft); 28799 28800 // Insert into the document to have it floating around 28801 28802 this.dom.clone = clone; 28803 this.dom.cloneParent = cloneParent; 28804 this.s.domCloneOuterHeight = clone.outerHeight(); 28805 }, 28806 28807 28808 /** 28809 * Update the cloned item's position in the document 28810 * 28811 * @param {object} e Event giving the mouse's position 28812 * @private 28813 */ 28814 _clonePosition: function ( e ) 28815 { 28816 var start = this.s.start; 28817 var topDiff = this._eventToPage( e, 'Y' ) - start.top; 28818 var leftDiff = this._eventToPage( e, 'X' ) - start.left; 28819 var snap = this.c.snapX; 28820 var left; 28821 var top = topDiff + start.offsetTop; 28822 28823 if ( snap === true ) { 28824 left = start.offsetLeft; 28825 } 28826 else if ( typeof snap === 'number' ) { 28827 left = start.offsetLeft + snap; 28828 } 28829 else { 28830 left = leftDiff + start.offsetLeft + this.dom.cloneParent.scrollLeft(); 28831 } 28832 28833 if(top < 0) { 28834 top = 0 28835 } 28836 else if(top + this.s.domCloneOuterHeight > this.s.documentOuterHeight) { 28837 top = this.s.documentOuterHeight - this.s.domCloneOuterHeight; 28838 } 28839 28840 this.dom.cloneParent.css( { 28841 top: top, 28842 left: left 28843 } ); 28844 }, 28845 28846 28847 /** 28848 * Emit an event on the DataTable for listeners 28849 * 28850 * @param {string} name Event name 28851 * @param {array} args Event arguments 28852 * @private 28853 */ 28854 _emitEvent: function ( name, args ) 28855 { 28856 this.s.dt.iterator( 'table', function ( ctx, i ) { 28857 $(ctx.nTable).triggerHandler( name+'.dt', args ); 28858 } ); 28859 }, 28860 28861 28862 /** 28863 * Get pageX/Y position from an event, regardless of if it is a mouse or 28864 * touch event. 28865 * 28866 * @param {object} e Event 28867 * @param {string} pos X or Y (must be a capital) 28868 * @private 28869 */ 28870 _eventToPage: function ( e, pos ) 28871 { 28872 if ( e.type.indexOf( 'touch' ) !== -1 ) { 28873 return e.originalEvent.touches[0][ 'page'+pos ]; 28874 } 28875 28876 return e[ 'page'+pos ]; 28877 }, 28878 28879 28880 /** 28881 * Mouse down event handler. Read initial positions and add event handlers 28882 * for the move. 28883 * 28884 * @param {object} e Mouse event 28885 * @param {jQuery} target TR element that is to be moved 28886 * @private 28887 */ 28888 _mouseDown: function ( e, target ) 28889 { 28890 var that = this; 28891 var dt = this.s.dt; 28892 var start = this.s.start; 28893 28894 var offset = target.offset(); 28895 start.top = this._eventToPage( e, 'Y' ); 28896 start.left = this._eventToPage( e, 'X' ); 28897 start.offsetTop = offset.top; 28898 start.offsetLeft = offset.left; 28899 start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); 28900 28901 this._cachePositions(); 28902 this._clone( target ); 28903 this._clonePosition( e ); 28904 28905 this.dom.target = target; 28906 target.addClass( 'dt-rowReorder-moving' ); 28907 28908 $( document ) 28909 .on( 'mouseup.rowReorder touchend.rowReorder', function (e) { 28910 that._mouseUp(e); 28911 } ) 28912 .on( 'mousemove.rowReorder touchmove.rowReorder', function (e) { 28913 that._mouseMove(e); 28914 } ); 28915 28916 // Check if window is x-scrolling - if not, disable it for the duration 28917 // of the drag 28918 if ( $(window).width() === $(document).width() ) { 28919 $(document.body).addClass( 'dt-rowReorder-noOverflow' ); 28920 } 28921 28922 // Cache scrolling information so mouse move doesn't need to read. 28923 // This assumes that the window and DT scroller will not change size 28924 // during an row drag, which I think is a fair assumption 28925 var scrollWrapper = this.dom.dtScroll; 28926 this.s.scroll = { 28927 windowHeight: $(window).height(), 28928 windowWidth: $(window).width(), 28929 dtTop: scrollWrapper.length ? scrollWrapper.offset().top : null, 28930 dtLeft: scrollWrapper.length ? scrollWrapper.offset().left : null, 28931 dtHeight: scrollWrapper.length ? scrollWrapper.outerHeight() : null, 28932 dtWidth: scrollWrapper.length ? scrollWrapper.outerWidth() : null 28933 }; 28934 }, 28935 28936 28937 /** 28938 * Mouse move event handler - move the cloned row and shuffle the table's 28939 * rows if required. 28940 * 28941 * @param {object} e Mouse event 28942 * @private 28943 */ 28944 _mouseMove: function ( e ) 28945 { 28946 this._clonePosition( e ); 28947 28948 // Transform the mouse position into a position in the table's body 28949 var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop; 28950 var middles = this.s.middles; 28951 var insertPoint = null; 28952 var dt = this.s.dt; 28953 28954 // Determine where the row should be inserted based on the mouse 28955 // position 28956 for ( var i=0, ien=middles.length ; i<ien ; i++ ) { 28957 if ( bodyY < middles[i] ) { 28958 insertPoint = i; 28959 break; 28960 } 28961 } 28962 28963 if ( insertPoint === null ) { 28964 insertPoint = middles.length; 28965 } 28966 28967 // Perform the DOM shuffle if it has changed from last time 28968 if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) { 28969 var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); 28970 28971 if ( insertPoint > this.s.lastInsert ) {
28972 this.dom.target.insertAfter( nodes[ insertPoint-1 ] ); 28973 } 28974 else { 28975 this.dom.target.insertBefore( nodes[ insertPoint ] ); 28976 } 28977 28978 this._cachePositions(); 28979 28980 this.s.lastInsert = insertPoint; 28981 } 28982 28983 this._shiftScroll( e ); 28984 }, 28985 28986 28987 /** 28988 * Mouse up event handler - release the event handlers and perform the 28989 * table updates 28990 * 28991 * @param {object} e Mouse event 28992 * @private 28993 */ 28994 _mouseUp: function ( e ) 28995 { 28996 var that = this; 28997 var dt = this.s.dt; 28998 var i, ien; 28999 var dataSrc = this.c.dataSrc; 29000 29001 this.dom.clone.remove(); 29002 this.dom.cloneParent.remove(); 29003 this.dom.clone = null; 29004 this.dom.cloneParent = null; 29005 29006 this.dom.target.removeClass( 'dt-rowReorder-moving' ); 29007 //this.dom.target = null; 29008 29009 $(document).off( '.rowReorder' ); 29010 $(document.body).removeClass( 'dt-rowReorder-noOverflow' ); 29011 29012 clearInterval( this.s.scrollInterval ); 29013 this.s.scrollInterval = null; 29014 29015 // Calculate the difference 29016 var startNodes = this.s.start.nodes; 29017 var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); 29018 var idDiff = {}; 29019 var fullDiff = []; 29020 var diffNodes = []; 29021 var getDataFn = this.s.getDataFn; 29022 var setDataFn = this.s.setDataFn; 29023 29024 for ( i=0, ien=startNodes.length ; i<ien ; i++ ) { 29025 if ( startNodes[i] !== endNodes[i] ) { 29026 var id = dt.row( endNodes[i] ).id(); 29027 var endRowData = dt.row( endNodes[i] ).data(); 29028 var startRowData = dt.row( startNodes[i] ).data(); 29029 29030 if ( id ) { 29031 idDiff[ id ] = getDataFn( startRowData ); 29032 } 29033 29034 fullDiff.push( { 29035 node: endNodes[i], 29036 oldData: getDataFn( endRowData ), 29037 newData: getDataFn( startRowData ), 29038 newPosition: i, 29039 oldPosition: $.inArray( endNodes[i], startNodes ) 29040 } ); 29041 29042 diffNodes.push( endNodes[i] ); 29043 } 29044 } 29045 29046 // Create event args 29047 var eventArgs = [ fullDiff, { 29048 dataSrc: dataSrc, 29049 nodes: diffNodes, 29050 values: idDiff, 29051 triggerRow: dt.row( this.dom.target ), 29052 originalEvent: e 29053 } ]; 29054 29055 // Emit event 29056 this._emitEvent( 'row-reorder', eventArgs ); 29057 29058 var update = function () { 29059 if ( that.c.update ) { 29060 for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) { 29061 var row = dt.row( fullDiff[i].node ); 29062 var rowData = row.data(); 29063 29064 setDataFn( rowData, fullDiff[i].newData ); 29065 29066 // Invalidate the cell that has the same data source as the dataSrc 29067 dt.columns().every( function () { 29068 if ( this.dataSrc() === dataSrc ) { 29069 dt.cell( fullDiff[i].node, this.index() ).invalidate( 'data' ); 29070 } 29071 } ); 29072 } 29073 29074 // Trigger row reordered event 29075 that._emitEvent( 'row-reordered', eventArgs ); 29076 29077 dt.draw( false ); 29078 } 29079 }; 29080 29081 // Editor interface 29082 if ( this.c.editor ) { 29083 // Disable user interaction while Editor is submitting 29084 this.c.enable = false; 29085 29086 this.c.editor 29087 .edit( 29088 diffNodes, 29089 false, 29090 $.extend( {submit: 'changed'}, this.c.formOptions ) 29091 ) 29092 .multiSet( dataSrc, idDiff ) 29093 .one( 'preSubmitCancelled.rowReorder', function () { 29094 that.c.enable = true; 29095 that.c.editor.off( '.rowReorder' ); 29096 dt.draw( false ); 29097 } ) 29098 .one( 'submitUnsuccessful.rowReorder', function () { 29099 dt.draw( false ); 29100 } ) 29101 .one( 'submitSuccess.rowReorder', function () { 29102 update(); 29103 } ) 29104 .one( 'submitComplete', function () { 29105 that.c.enable = true; 29106 that.c.editor.off( '.rowReorder' ); 29107 } ) 29108 .submit(); 29109 } 29110 else { 29111 update(); 29112 } 29113 }, 29114 29115 29116 /** 29117 * Move the window and DataTables scrolling during a drag to scroll new 29118 * content into view. 29119 * 29120 * This matches the `_shiftScroll` method used in AutoFill, but only 29121 * horizontal scrolling is considered here. 29122 * 29123 * @param {object} e Mouse move event object 29124 * @private 29125 */ 29126 _shiftScroll: function ( e ) 29127 { 29128 var that = this; 29129 var dt = this.s.dt; 29130 var scroll = this.s.scroll; 29131 var runInterval = false; 29132 var scrollSpeed = 5; 29133 var buffer = 65; 29134 var 29135 windowY = e.pageY - document.body.scrollTop, 29136 windowVert, 29137 dtVert; 29138 29139 // Window calculations - based on the mouse position in the window, 29140 // regardless of scrolling 29141 if ( windowY < $(window).scrollTop() + buffer ) { 29142 windowVert = scrollSpeed * -1; 29143 } 29144 else if ( windowY > scroll.windowHeight + $(window).scrollTop() - buffer ) { 29145 windowVert = scrollSpeed; 29146 } 29147 29148 // DataTables scrolling calculations - based on the table's position in 29149 // the document and the mouse position on the page 29150 if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) { 29151 dtVert = scrollSpeed * -1; 29152 } 29153 else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) { 29154 dtVert = scrollSpeed; 29155 } 29156 29157 // This is where it gets interesting. We want to continue scrolling 29158 // without requiring a mouse move, so we need an interval to be 29159 // triggered. The interval should continue until it is no longer needed, 29160 // but it must also use the latest scroll commands (for example consider 29161 // that the mouse might move from scrolling up to scrolling left, all 29162 // with the same interval running. We use the `scroll` object to "pass" 29163 // this information to the interval. Can't use local variables as they 29164 // wouldn't be the ones that are used by an already existing interval! 29165 if ( windowVert || dtVert ) { 29166 scroll.windowVert = windowVert; 29167 scroll.dtVert = dtVert; 29168 runInterval = true; 29169 } 29170 else if ( this.s.scrollInterval ) { 29171 // Don't need to scroll - remove any existing timer 29172 clearInterval( this.s.scrollInterval ); 29173 this.s.scrollInterval = null; 29174 } 29175 29176 // If we need to run the interval to scroll and there is no existing 29177 // interval (if there is an existing one, it will continue to run) 29178 if ( ! this.s.scrollInterval && runInterval ) { 29179 this.s.scrollInterval = setInterval( function () { 29180 // Don't need to worry about setting scroll <0 or beyond the 29181 // scroll bound as the browser will just reject that. 29182 if ( scroll.windowVert ) { 29183 var top = $(document).scrollTop(); 29184 $(document).scrollTop(top + scroll.windowVert); 29185 29186 if ( top !== $(document).scrollTop() ) { 29187 var move = parseFloat(that.dom.cloneParent.css("top")); 29188 that.dom.cloneParent.css("top", move + scroll.windowVert); 29189 } 29190 } 29191 29192 // DataTables scrolling 29193 if ( scroll.dtVert ) { 29194 var scroller = that.dom.dtScroll[0]; 29195 29196 if ( scroll.dtVert ) { 29197 scroller.scrollTop += scroll.dtVert; 29198 } 29199 } 29200 }, 20 ); 29201 } 29202 } 29203} ); 29204 29205 29206 29207/** 29208 * RowReorder default settings for initialisation 29209 * 29210 * @namespace 29211 * @name RowReorder.defaults 29212 * @static 29213 */ 29214RowReorder.defaults = { 29215 /** 29216 * Data point in the host row's data source object for where to get and set 29217 * the data to reorder. This will normally also be the sorting column. 29218 * 29219 * @type {Number} 29220 */ 29221 dataSrc: 0, 29222 29223 /** 29224 * Editor instance that will be used to perform the update 29225 * 29226 * @type {DataTable.Editor} 29227 */ 29228 editor: null, 29229 29230 /** 29231 * Enable / disable RowReorder's user interaction 29232 * @type {Boolean} 29233 */ 29234 enable: true, 29235 29236 /** 29237 * Form options to pass to Editor when submitting a change in the row order. 29238 * See the Editor `from-options` object for details of the options 29239 * available. 29240 * @type {Object} 29241 */ 29242 formOptions: {}, 29243 29244 /** 29245 * Drag handle selector. This defines the element that when dragged will 29246 * reorder a row. 29247 * 29248 * @type {String} 29249 */ 29250 selector: 'td:first-child', 29251 29252 /** 29253 * Optionally lock the dragged row's x-position. This can be `true` to 29254 * fix the position match the host table's, `false` to allow free movement 29255 * of the row, or a number to define an offset from the host table. 29256 * 29257 * @type {Boolean|number} 29258 */ 29259 snapX: false, 29260 29261 /** 29262 * Update the table's data on drop 29263 * 29264 * @type {Boolean} 29265 */ 29266 update: true, 29267 29268 /** 29269 * Selector for children of the drag handle selector that mouseDown events 29270 * will be passed through to and drag will not activate 29271 * 29272 * @type {String} 29273 */ 29274 excludedChildren: 'a' 29275}; 29276 29277 29278/* 29279 * API 29280 */ 29281var Api = $.fn.dataTable.Api; 29282 29283// Doesn't do anything - work around for a bug in DT... Not documented 29284Api.register( 'rowReorder()', function () { 29285 return this; 29286} ); 29287 29288Api.register( 'rowReorder.enable()', function ( toggle ) { 29289 if ( toggle === undefined ) { 29290 toggle = true; 29291 } 29292 29293 return this.iterator( 'table', function ( ctx ) { 29294 if ( ctx.rowreorder ) { 29295 ctx.rowreorder.c.enable = toggle; 29296 } 29297 } ); 29298} ); 29299 29300Api.register( 'rowReorder.disable()', function () { 29301 return this.iterator( 'table', function ( ctx ) { 29302 if ( ctx.rowreorder ) { 29303 ctx.rowreorder.c.enable = false; 29304 } 29305 } ); 29306} ); 29307 29308 29309/** 29310 * Version information 29311 * 29312 * @name RowReorder.version 29313 * @static 29314 */ 29315RowReorder.version = '1.3.3'; 29316 29317 29318$.fn.dataTable.RowReorder = RowReorder; 29319$.fn.DataTable.RowReorder = RowReorder; 29320 29321// Attach a listener to the document which listens for DataTables initialisation 29322// events so we can automatically initialise 29323$(document).on( 'init.dt.dtr', function (e, settings, json) { 29324 if ( e.namespace !== 'dt' ) { 29325 return; 29326 } 29327 29328 var init = settings.oInit.rowReorder; 29329 var defaults = DataTable.defaults.rowReorder; 29330 29331 if ( init || defaults ) { 29332 var opts = $.extend( {}, init, defaults ); 29333 29334 if ( init !== false ) { 29335 new RowReorder( settings, opts ); 29336 } 29337 } 29338} ); 29339 29340 29341return DataTable; 29342})); 29343 29344 29345/*! Scroller 2.1.1 29346 * © SpryMedia Ltd - datatables.net/license 29347 */ 29348 29349(function( factory ){ 29350 if ( typeof define === 'function' && define.amd ) { 29351 // AMD 29352 define( ['jquery', 'datatables.net'], function ( $ ) { 29353 return factory( $, window, document ); 29354 } ); 29355 } 29356 else if ( typeof exports === 'object' ) { 29357 // CommonJS 29358 var jq = require('jquery'); 29359 var cjsRequires = function (root, $) { 29360 if ( ! $.fn.dataTable ) { 29361 require('datatables.net')(root, $); 29362 } 29363 }; 29364 29365 if (typeof window !== 'undefined') { 29366 module.exports = function (root, $) { 29367 if ( ! root ) { 29368 // CommonJS environments without a window global must pass a 29369 // root. This will give an error otherwise 29370 root = window; 29371 } 29372 29373 if ( ! $ ) { 29374 $ = jq( root ); 29375 } 29376 29377 cjsRequires( root, $ ); 29378 return factory( $, root, root.document ); 29379 }; 29380 } 29381 else { 29382 cjsRequires( window, jq ); 29383 module.exports = factory( jq, window, window.document ); 29384 } 29385 } 29386 else { 29387 // Browser 29388 factory( jQuery, window, document ); 29389 } 29390}(function( $, window, document, undefined ) { 29391'use strict'; 29392var DataTable = $.fn.dataTable; 29393 29394 29395 29396/** 29397 * @summary Scroller 29398 * @description Virtual rendering for DataTables 29399 * @version 2.1.1 29400 * @author SpryMedia Ltd (www.sprymedia.co.uk) 29401 * @contact www.sprymedia.co.uk/contact 29402 * @copyright SpryMedia Ltd. 29403 * 29404 * This source file is free software, available under the following license:
29405 * MIT license - http://datatables.net/license/mit 29406 * 29407 * This source file is distributed in the hope that it will be useful, but 29408 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 29409 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 29410 * 29411 * For details please refer to: http://www.datatables.net 29412 */ 29413 29414 29415/** 29416 * Scroller is a virtual rendering plug-in for DataTables which allows large 29417 * datasets to be drawn on screen every quickly. What the virtual rendering means 29418 * is that only the visible portion of the table (and a bit to either side to make 29419 * the scrolling smooth) is drawn, while the scrolling container gives the 29420 * visual impression that the whole table is visible. This is done by making use 29421 * of the pagination abilities of DataTables and moving the table around in the 29422 * scrolling container DataTables adds to the page. The scrolling container is 29423 * forced to the height it would be for the full table display using an extra 29424 * element. 29425 * 29426 * Note that rows in the table MUST all be the same height. Information in a cell 29427 * which expands on to multiple lines will cause some odd behaviour in the scrolling. 29428 * 29429 * Scroller is initialised by simply including the letter 'S' in the sDom for the 29430 * table you want to have this feature enabled on. Note that the 'S' must come 29431 * AFTER the 't' parameter in `dom`. 29432 * 29433 * Key features include: 29434 * <ul class="limit_length"> 29435 * <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li> 29436 * <li>Full compatibility with deferred rendering in DataTables for maximum speed</li> 29437 * <li>Display millions of rows</li> 29438 * <li>Integration with state saving in DataTables (scrolling position is saved)</li> 29439 * <li>Easy to use</li> 29440 * </ul> 29441 * 29442 * @class 29443 * @constructor 29444 * @global 29445 * @param {object} dt DataTables settings object or API instance 29446 * @param {object} [opts={}] Configuration object for Scroller. Options 29447 * are defined by {@link Scroller.defaults} 29448 * 29449 * @requires jQuery 1.7+ 29450 * @requires DataTables 1.10.0+ 29451 * 29452 * @example 29453 * $(document).ready(function() { 29454 * $('#example').DataTable( { 29455 * "scrollY": "200px", 29456 * "ajax": "media/dataset/large.txt", 29457 * "scroller": true, 29458 * "deferRender": true 29459 * } ); 29460 * } ); 29461 */ 29462var Scroller = function ( dt, opts ) { 29463 /* Sanity check - you just know it will happen */ 29464 if ( ! (this instanceof Scroller) ) { 29465 alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." ); 29466 return; 29467 } 29468 29469 if ( opts === undefined ) { 29470 opts = {}; 29471 } 29472
29473 var dtApi = $.fn.dataTable.Api( dt ); 29474 29475 /** 29476 * Settings object which contains customisable information for the Scroller instance 29477 * @namespace 29478 * @private 29479 * @extends Scroller.defaults 29480 */ 29481 this.s = { 29482 /** 29483 * DataTables settings object 29484 * @type object 29485 * @default Passed in as first parameter to constructor 29486 */ 29487 dt: dtApi.settings()[0], 29488 29489 /** 29490 * DataTables API instance 29491 * @type DataTable.Api 29492 */ 29493 dtApi: dtApi, 29494 29495 /** 29496 * Pixel location of the top of the drawn table in the viewport 29497 * @type int 29498 * @default 0 29499 */ 29500 tableTop: 0, 29501 29502 /** 29503 * Pixel location of the bottom of the drawn table in the viewport 29504 * @type int 29505 * @default 0 29506 */ 29507 tableBottom: 0, 29508 29509 /** 29510 * Pixel location of the boundary for when the next data set should be loaded and drawn 29511 * when scrolling up the way. 29512 * @type int 29513 * @default 0 29514 * @private 29515 */ 29516 redrawTop: 0, 29517 29518 /** 29519 * Pixel location of the boundary for when the next data set should be loaded and drawn 29520 * when scrolling down the way. Note that this is actually calculated as the offset from 29521 * the top. 29522 * @type int 29523 * @default 0 29524 * @private 29525 */ 29526 redrawBottom: 0, 29527 29528 /** 29529 * Auto row height or not indicator 29530 * @type bool 29531 * @default 0 29532 */ 29533 autoHeight: true, 29534 29535 /** 29536 * Number of rows calculated as visible in the visible viewport 29537 * @type int 29538 * @default 0 29539 */ 29540 viewportRows: 0, 29541 29542 /** 29543 * setTimeout reference for state saving, used when state saving is enabled in the DataTable 29544 * and when the user scrolls the viewport in order to stop the cookie set taking too much 29545 * CPU! 29546 * @type int 29547 * @default 0 29548 */ 29549 stateTO: null, 29550 29551 stateSaveThrottle: function () {}, 29552 29553 /** 29554 * setTimeout reference for the redraw, used when server-side processing is enabled in the 29555 * DataTables in order to prevent DoSing the server 29556 * @type int 29557 * @default null 29558 */ 29559 drawTO: null, 29560 29561 heights: { 29562 jump: null, 29563 page: null, 29564 virtual: null, 29565 scroll: null, 29566 29567 /** 29568 * Height of rows in the table 29569 * @type int 29570 * @default 0 29571 */ 29572 row: null, 29573 29574 /** 29575 * Pixel height of the viewport 29576 * @type int 29577 * @default 0 29578 */ 29579 viewport: null, 29580 labelHeight: 0, 29581 xbar: 0 29582 }, 29583 29584 topRowFloat: 0, 29585 scrollDrawDiff: null, 29586 loaderVisible: false, 29587 forceReposition: false, 29588 baseRowTop: 0, 29589 baseScrollTop: 0, 29590 mousedown: false, 29591 lastScrollTop: 0 29592 }; 29593 29594 // @todo The defaults should extend a `c` property and the internal settings 29595 // only held in the `s` property. At the moment they are mixed 29596 this.s = $.extend( this.s, Scroller.oDefaults, opts ); 29597 29598 // Workaround for row height being read from height object (see above comment) 29599 this.s.heights.row = this.s.rowHeight; 29600 29601 /** 29602 * DOM elements used by the class instance 29603 * @private 29604 * @namespace 29605 * 29606 */ 29607 this.dom = { 29608 "force": document.createElement('div'), 29609 "label": $('<div class="dts_label">0</div>'), 29610 "scroller": null, 29611 "table": null, 29612 "loader": null 29613 }; 29614 29615 // Attach the instance to the DataTables instance so it can be accessed in 29616 // future. Don't initialise Scroller twice on the same table 29617 if ( this.s.dt.oScroller ) { 29618 return; 29619 } 29620 29621 this.s.dt.oScroller = this; 29622 29623 /* Let's do it */ 29624 this.construct(); 29625}; 29626 29627 29628 29629$.extend( Scroller.prototype, { 29630 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 29631 * Public methods - to be exposed via the DataTables API 29632 */ 29633 29634 /** 29635 * Calculate and store information about how many rows are to be displayed 29636 * in the scrolling viewport, based on current dimensions in the browser's 29637 * rendering. This can be particularly useful if the table is initially 29638 * drawn in a hidden element - for example in a tab. 29639 * @param {bool} [redraw=true] Redraw the table automatically after the recalculation, with 29640 * the new dimensions forming the basis for the draw. 29641 * @returns {void} 29642 */ 29643 measure: function ( redraw ) 29644 { 29645 if ( this.s.autoHeight ) 29646 { 29647 this._calcRowHeight(); 29648 } 29649 29650 var heights = this.s.heights; 29651 29652 if ( heights.row ) { 29653 heights.viewport = this._parseHeight($(this.dom.scroller).css('max-height')); 29654 29655 this.s.viewportRows = parseInt( heights.viewport / heights.row, 10 )+1; 29656 this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer; 29657 } 29658 29659 var label = this.dom.label.outerHeight(); 29660 29661 heights.xbar = this.dom.scroller.offsetHeight - this.dom.scroller.clientHeight; 29662 heights.labelHeight = label; 29663 29664 if ( redraw === undefined || redraw ) 29665 { 29666 this.s.dt.oInstance.fnDraw( false ); 29667 } 29668 }, 29669 29670 /** 29671 * Get information about current displayed record range. This corresponds to 29672 * the information usually displayed in the "Info" block of the table. 29673 * 29674 * @returns {object} info as an object: 29675 * { 29676 * start: {int}, // the 0-indexed record at the top of the viewport 29677 * end: {int}, // the 0-indexed record at the bottom of the viewport 29678 * } 29679 */ 29680 pageInfo: function() 29681 { 29682 var 29683 dt = this.s.dt, 29684 iScrollTop = this.dom.scroller.scrollTop, 29685 iTotal = dt.fnRecordsDisplay(), 29686 iPossibleEnd = Math.ceil(this.pixelsToRow(iScrollTop + this.s.heights.viewport, false, this.s.ani)); 29687 29688 return { 29689 start: Math.floor(this.pixelsToRow(iScrollTop, false, this.s.ani)), 29690 end: iTotal < iPossibleEnd ? iTotal-1 : iPossibleEnd-1 29691 }; 29692 }, 29693 29694 /** 29695 * Calculate the row number that will be found at the given pixel position 29696 * (y-scroll). 29697 * 29698 * Please note that when the height of the full table exceeds 1 million 29699 * pixels, Scroller switches into a non-linear mode for the scrollbar to fit 29700 * all of the records into a finite area, but this function returns a linear 29701 * value (relative to the last non-linear positioning). 29702 * @param {int} pixels Offset from top to calculate the row number of 29703 * @param {int} [intParse=true] If an integer value should be returned 29704 * @param {int} [virtual=false] Perform the calculations in the virtual domain 29705 * @returns {int} Row index 29706 */ 29707 pixelsToRow: function ( pixels, intParse, virtual ) 29708 { 29709 var diff = pixels - this.s.baseScrollTop; 29710 var row = virtual ? 29711 (this._domain( 'physicalToVirtual', this.s.baseScrollTop ) + diff) / this.s.heights.row : 29712 ( diff / this.s.heights.row ) + this.s.baseRowTop; 29713 29714 return intParse || intParse === undefined ? 29715 parseInt( row, 10 ) : 29716 row; 29717 }, 29718 29719 /** 29720 * Calculate the pixel position from the top of the scrolling container for 29721 * a given row 29722 * @param {int} iRow Row number to calculate the position of 29723 * @returns {int} Pixels 29724 */ 29725 rowToPixels: function ( rowIdx, intParse, virtual ) 29726 { 29727 var pixels; 29728 var diff = rowIdx - this.s.baseRowTop; 29729 29730 if ( virtual ) { 29731 pixels = this._domain( 'virtualToPhysical', this.s.baseScrollTop ); 29732 pixels += diff * this.s.heights.row; 29733 } 29734 else { 29735 pixels = this.s.baseScrollTop; 29736 pixels += diff * this.s.heights.row; 29737 } 29738 29739 return intParse || intParse === undefined ? 29740 parseInt( pixels, 10 ) : 29741 pixels; 29742 }, 29743 29744 29745 /** 29746 * Calculate the row number that will be found at the given pixel position (y-scroll) 29747 * @param {int} row Row index to scroll to 29748 * @param {bool} [animate=true] Animate the transition or not 29749 * @returns {void} 29750 */ 29751 scrollToRow: function ( row, animate ) 29752 { 29753 var that = this; 29754 var ani = false; 29755 var px = this.rowToPixels( row ); 29756 29757 // We need to know if the table will redraw or not before doing the 29758 // scroll. If it will not redraw, then we need to use the currently 29759 // displayed table, and scroll with the physical pixels. Otherwise, we 29760 // need to calculate the table's new position from the virtual 29761 // transform. 29762 var preRows = ((this.s.displayBuffer-1)/2) * this.s.viewportRows; 29763 var drawRow = row - preRows; 29764 if ( drawRow < 0 ) { 29765 drawRow = 0; 29766 } 29767 29768 if ( (px > this.s.redrawBottom || px < this.s.redrawTop) && this.s.dt._iDisplayStart !== drawRow ) { 29769 ani = true; 29770 px = this._domain( 'virtualToPhysical', row * this.s.heights.row ); 29771 29772 // If we need records outside the current draw region, but the new 29773 // scrolling position is inside that (due to the non-linear nature 29774 // for larger numbers of records), we need to force position update. 29775 if ( this.s.redrawTop < px && px < this.s.redrawBottom ) { 29776 this.s.forceReposition = true; 29777 animate = false; 29778 } 29779 } 29780 29781 if ( animate === undefined || animate ) 29782 { 29783 this.s.ani = ani; 29784 $(this.dom.scroller).animate( { 29785 "scrollTop": px 29786 }, function () { 29787 // This needs to happen after the animation has completed and 29788 // the final scroll event fired 29789 setTimeout( function () { 29790 that.s.ani = false; 29791 }, 250 ); 29792 } ); 29793 } 29794 else 29795 { 29796 $(this.dom.scroller).scrollTop( px ); 29797 } 29798 }, 29799 29800 29801 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 29802 * Constructor 29803 */ 29804 29805 /** 29806 * Initialisation for Scroller 29807 * @returns {void} 29808 * @private 29809 */ 29810 construct: function () 29811 { 29812 var that = this; 29813 var dt = this.s.dtApi; 29814 29815 /* Sanity check */ 29816 if ( !this.s.dt.oFeatures.bPaginate ) { 29817 this.s.dt.oApi._fnLog( this.s.dt, 0, 'Pagination must be enabled for Scroller' ); 29818 return; 29819 } 29820 29821 /* Insert a div element that we can use to force the DT scrolling container to 29822 * the height that would be required if the whole table was being displayed 29823 */ 29824 this.dom.force.style.position = "relative"; 29825 this.dom.force.style.top = "0px";
29826 this.dom.force.style.left = "0px"; 29827 this.dom.force.style.width = "1px"; 29828 29829 this.dom.scroller = $('div.'+this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0]; 29830 this.dom.scroller.appendChild( this.dom.force ); 29831 this.dom.scroller.style.position = "relative"; 29832 29833 this.dom.table = $('>table', this.dom.scroller)[0]; 29834 this.dom.table.style.position = "absolute"; 29835 this.dom.table.style.top = "0px"; 29836 this.dom.table.style.left = "0px"; 29837 29838 // Add class to 'announce' that we are a Scroller table 29839 $(dt.table().container()).addClass('dts DTS'); 29840 29841 // Add a 'loading' indicator 29842 if ( this.s.loadingIndicator ) 29843 { 29844 this.dom.loader = $('<div class="dataTables_processing dts_loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>') 29845 .css('display', 'none'); 29846 29847 $(this.dom.scroller.parentNode) 29848 .css('position', 'relative') 29849 .append( this.dom.loader ); 29850 } 29851 29852 this.dom.label.appendTo(this.dom.scroller); 29853 29854 /* Initial size calculations */ 29855 if ( this.s.heights.row && this.s.heights.row != 'auto' ) 29856 { 29857 this.s.autoHeight = false; 29858 } 29859 29860 // Scrolling callback to see if a page change is needed 29861 this.s.ingnoreScroll = true; 29862 $(this.dom.scroller).on( 'scroll.dt-scroller', function (e) { 29863 that._scroll.call( that ); 29864 } ); 29865 29866 // In iOS we catch the touchstart event in case the user tries to scroll 29867 // while the display is already scrolling 29868 $(this.dom.scroller).on('touchstart.dt-scroller', function () { 29869 that._scroll.call( that ); 29870 } ); 29871 29872 $(this.dom.scroller) 29873 .on('mousedown.dt-scroller', function () { 29874 that.s.mousedown = true; 29875 }) 29876 .on('mouseup.dt-scroller', function () { 29877 that.s.labelVisible = false; 29878 that.s.mousedown = false; 29879 that.dom.label.css('display', 'none'); 29880 }); 29881 29882 // On resize, update the information element, since the number of rows shown might change 29883 $(window).on( 'resize.dt-scroller', function () { 29884 that.measure( false ); 29885 that._info(); 29886 } ); 29887 29888 // Add a state saving parameter to the DT state saving so we can restore the exact 29889 // position of the scrolling. 29890 var initialStateSave = true; 29891 var loadedState = dt.state.loaded(); 29892 29893 dt.on( 'stateSaveParams.scroller', function ( e, settings, data ) { 29894 if ( initialStateSave && loadedState ) { 29895 data.scroller = loadedState.scroller; 29896 initialStateSave = false; 29897 29898 if (data.scroller) { 29899 that.s.lastScrollTop = data.scroller.scrollTop; 29900 } 29901 } 29902 else { 29903 // Need to used the saved position on init 29904 data.scroller = { 29905 topRow: that.s.topRowFloat, 29906 baseScrollTop: that.s.baseScrollTop, 29907 baseRowTop: that.s.baseRowTop, 29908 scrollTop: that.s.lastScrollTop 29909 }; 29910 } 29911 } ); 29912 29913 dt.on( 'stateLoadParams.scroller', function( e, settings, data ) { 29914 if (data.scroller !== undefined) { 29915 that.scrollToRow(data.scroller.topRow); 29916 } 29917 }); 29918 29919 if ( loadedState && loadedState.scroller ) { 29920 this.s.topRowFloat = loadedState.scroller.topRow; 29921 this.s.baseScrollTop = loadedState.scroller.baseScrollTop; 29922 this.s.baseRowTop = loadedState.scroller.baseRowTop; 29923 } 29924 29925 this.measure( false ); 29926 29927 that.s.stateSaveThrottle = that.s.dt.oApi._fnThrottle( function () { 29928 that.s.dtApi.state.save(); 29929 }, 500 ); 29930 29931 dt.on( 'init.scroller', function () { 29932 that.measure( false ); 29933 29934 // Setting to `jump` will instruct _draw to calculate the scroll top 29935 // position 29936 that.s.scrollType = 'jump'; 29937 that._draw(); 29938 29939 // Update the scroller when the DataTable is redrawn 29940 dt.on( 'draw.scroller', function () { 29941 that._draw(); 29942 }); 29943 } ); 29944 29945 // Set height before the draw happens, allowing everything else to update 29946 // on draw complete without worry for roder. 29947 dt.on( 'preDraw.dt.scroller', function () { 29948 that._scrollForce(); 29949 } ); 29950 29951 // Destructor 29952 dt.on( 'destroy.scroller', function () { 29953 $(window).off( 'resize.dt-scroller' ); 29954 $(that.dom.scroller).off('.dt-scroller'); 29955 $(that.s.dt.nTable).off( '.scroller' ); 29956 29957 $(that.s.dt.nTableWrapper).removeClass('DTS'); 29958 $('div.DTS_Loading', that.dom.scroller.parentNode).remove(); 29959 29960 that.dom.table.style.position = ""; 29961 that.dom.table.style.top = ""; 29962 that.dom.table.style.left = ""; 29963 } ); 29964 }, 29965 29966 29967 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 29968 * Private methods 29969 */ 29970 29971 /** 29972 * Automatic calculation of table row height. This is just a little tricky here as using 29973 * initialisation DataTables has tale the table out of the document, so we need to create 29974 * a new table and insert it into the document, calculate the row height and then whip the 29975 * table out. 29976 * @returns {void} 29977 * @private 29978 */ 29979 _calcRowHeight: function () 29980 { 29981 var dt = this.s.dt; 29982 var origTable = dt.nTable; 29983 var nTable = origTable.cloneNode( false ); 29984 var tbody = $('<tbody/>').appendTo( nTable ); 29985 var container = $(
29986 '<div class="'+dt.oClasses.sWrapper+' DTS">'+ 29987 '<div class="'+dt.oClasses.sScrollWrapper+'">'+ 29988 '<div class="'+dt.oClasses.sScrollBody+'"></div>'+ 29989 '</div>'+ 29990 '</div>' 29991 ); 29992 29993 // Want 3 rows in the sizing table so :first-child and :last-child 29994 // CSS styles don't come into play - take the size of the middle row 29995 $('tbody tr:lt(4)', origTable).clone().appendTo( tbody ); 29996 var rowsCount = $('tr', tbody).length; 29997 29998 if ( rowsCount === 1 ) { 29999 tbody.prepend('<tr><td> </td></tr>'); 30000 tbody.append('<tr><td> </td></tr>'); 30001 } 30002 else { 30003 for (; rowsCount < 3; rowsCount++) { 30004 tbody.append('<tr><td> </td></tr>'); 30005 } 30006 } 30007 30008 $('div.'+dt.oClasses.sScrollBody, container).append( nTable ); 30009 30010 // If initialised using `dom`, use the holding element as the insert point 30011 var insertEl = this.s.dt.nHolding || origTable.parentNode; 30012 30013 if ( ! $(insertEl).is(':visible') ) { 30014 insertEl = 'body'; 30015 } 30016 30017 // Remove form element links as they might select over others (particularly radio and checkboxes) 30018 container.find("input").removeAttr("name"); 30019 30020 container.appendTo( insertEl ); 30021 this.s.heights.row = $('tr', tbody).eq(1).outerHeight(); 30022 30023 container.remove(); 30024 }, 30025 30026 /** 30027 * Draw callback function which is fired when the DataTable is redrawn. The main function of 30028 * this method is to position the drawn table correctly the scrolling container for the rows 30029 * that is displays as a result of the scrolling position. 30030 * @returns {void} 30031 * @private 30032 */ 30033 _draw: function () 30034 { 30035 var 30036 that = this, 30037 heights = this.s.heights, 30038 iScrollTop = this.dom.scroller.scrollTop, 30039 iTableHeight = $(this.s.dt.nTable).height(), 30040 displayStart = this.s.dt._iDisplayStart, 30041 displayLen = this.s.dt._iDisplayLength, 30042 displayEnd = this.s.dt.fnRecordsDisplay(), 30043 viewportEndY = iScrollTop + heights.viewport; 30044 30045 // Disable the scroll event listener while we are updating the DOM 30046 this.s.skip = true; 30047 30048 // If paging is reset 30049 if ( (this.s.dt.bSorted || this.s.dt.bFiltered) && displayStart === 0 && !this.s.dt._drawHold ) { 30050 this.s.topRowFloat = 0; 30051 } 30052 30053 iScrollTop = this.s.scrollType === 'jump' ? 30054 this._domain( 'virtualToPhysical', this.s.topRowFloat * heights.row ) : 30055 iScrollTop; 30056 30057 // Store positional information so positional calculations can be based 30058 // upon the current table draw position 30059 this.s.baseScrollTop = iScrollTop; 30060 this.s.baseRowTop = this.s.topRowFloat; 30061 30062 // Position the table in the virtual scroller 30063 var tableTop = iScrollTop - ((this.s.topRowFloat - displayStart) * heights.row); 30064 if ( displayStart === 0 ) { 30065 tableTop = 0; 30066 } 30067 else if ( displayStart + displayLen >= displayEnd ) { 30068 tableTop = heights.scroll - iTableHeight; 30069 } 30070 else { 30071 var iTableBottomY = tableTop + iTableHeight; 30072 if (iTableBottomY < viewportEndY) { 30073 // The last row of the data is above the end of the viewport. 30074 // This means the background is visible, which is not what the user expects. 30075 var newTableTop = viewportEndY - iTableHeight; 30076 var diffPx = newTableTop - tableTop; 30077 this.s.baseScrollTop += diffPx + 1; // Update start row number in footer. 30078 tableTop = newTableTop; // Move table so last line of data is at the bottom of the viewport. 30079 } 30080 } 30081 30082 this.dom.table.style.top = tableTop+'px'; 30083 30084 /* Cache some information for the scroller */ 30085 this.s.tableTop = tableTop; 30086 this.s.tableBottom = iTableHeight + this.s.tableTop; 30087 30088 // Calculate the boundaries for where a redraw will be triggered by the 30089 // scroll event listener 30090 var boundaryPx = (iScrollTop - this.s.tableTop) * this.s.boundaryScale; 30091 this.s.redrawTop = iScrollTop - boundaryPx; 30092 this.s.redrawBottom = iScrollTop + boundaryPx > heights.scroll - heights.viewport - heights.row ? 30093 heights.scroll - heights.viewport - heights.row : 30094 iScrollTop + boundaryPx; 30095 30096 this.s.skip = false; 30097 30098 if(that.s.ingnoreScroll) { 30099 // Restore the scrolling position that was saved by DataTable's state 30100 // saving Note that this is done on the second draw when data is Ajax 30101 // sourced, and the first draw when DOM soured 30102 if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null && 30103 typeof this.s.dt.oLoadedState.scroller != 'undefined' ) 30104 {
30105 // A quirk of DataTables is that the draw callback will occur on an 30106 // empty set if Ajax sourced, but not if server-side processing. 30107 var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && ! this.s.dt.oFeatures.bServerSide ? 30108 true : 30109 false; 30110 30111 if ( ( ajaxSourced && this.s.dt.iDraw >= 2) || 30112 (!ajaxSourced && this.s.dt.iDraw >= 1) ) 30113 { 30114 setTimeout( function () { 30115 $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.scroller.scrollTop ); 30116 30117 // In order to prevent layout thrashing we need another 30118 // small delay 30119 setTimeout( function () { 30120 that.s.ingnoreScroll = false; 30121 }, 0 ); 30122 }, 0 ); 30123 } 30124 } 30125 else { 30126 that.s.ingnoreScroll = false; 30127 } 30128 } 30129 30130 30131 // Because of the order of the DT callbacks, the info update will 30132 // take precedence over the one we want here. So a 'thread' break is 30133 // needed. Only add the thread break if bInfo is set 30134 if ( this.s.dt.oFeatures.bInfo ) { 30135 setTimeout( function () { 30136 that._info.call( that ); 30137 }, 0 ); 30138 } 30139 30140 $(this.s.dt.nTable).triggerHandler('position.dts.dt', tableTop); 30141 30142 // Hide the loading indicator 30143 if ( this.dom.loader && this.s.loaderVisible ) { 30144 this.dom.loader.css( 'display', 'none' ); 30145 this.s.loaderVisible = false; 30146 } 30147 }, 30148 30149 /** 30150 * Convert from one domain to another. The physical domain is the actual 30151 * pixel count on the screen, while the virtual is if we had browsers which 30152 * had scrolling containers of infinite height (i.e. the absolute value) 30153 * 30154 * @param {string} dir Domain transform direction, `virtualToPhysical` or 30155 * `physicalToVirtual` 30156 * @returns {number} Calculated transform 30157 * @private 30158 */ 30159 _domain: function ( dir, val ) 30160 { 30161 var heights = this.s.heights; 30162 var diff; 30163 var magic = 10000; // the point at which the non-linear calculations start to happen 30164 30165 // If the virtual and physical height match, then we use a linear 30166 // transform between the two, allowing the scrollbar to be linear 30167 if ( heights.virtual === heights.scroll ) { 30168 return val; 30169 } 30170 30171 // In the first 10k pixels and the last 10k pixels, we want the scrolling 30172 // to be linear. After that it can be non-linear. It would be unusual for 30173 // anyone to mouse wheel through that much. 30174 if ( val < magic ) { 30175 return val; 30176 } 30177 else if ( dir === 'virtualToPhysical' && val >= heights.virtual - magic ) { 30178 diff = heights.virtual - val; 30179 return heights.scroll - diff; 30180 } 30181 else if ( dir === 'physicalToVirtual' && val >= heights.scroll - magic ) { 30182 diff = heights.scroll - val; 30183 return heights.virtual - diff; 30184 } 30185 30186 // Otherwise, we want a non-linear scrollbar to take account of the 30187 // redrawing regions at the start and end of the table, otherwise these 30188 // can stutter badly - on large tables 30px (for example) scroll might 30189 // be hundreds of rows, so the table would be redrawing every few px at 30190 // the start and end. Use a simple linear eq. to stop this, effectively 30191 // causing a kink in the scrolling ratio. It does mean the scrollbar is 30192 // non-linear, but with such massive data sets, the scrollbar is going 30193 // to be a best guess anyway 30194 var m = (heights.virtual - magic - magic) / (heights.scroll - magic - magic); 30195 var c = magic - (m*magic); 30196 30197 return dir === 'virtualToPhysical' ? 30198 (val-c) / m : 30199 (m*val) + c; 30200 }, 30201 30202 /** 30203 * Update any information elements that are controlled by the DataTable based on the scrolling 30204 * viewport and what rows are visible in it. This function basically acts in the same way as 30205 * _fnUpdateInfo in DataTables, and effectively replaces that function. 30206 * @returns {void} 30207 * @private 30208 */ 30209 _info: function () 30210 { 30211 if ( !this.s.dt.oFeatures.bInfo ) 30212 { 30213 return; 30214 } 30215 30216 var 30217 dt = this.s.dt, 30218 language = dt.oLanguage, 30219 iScrollTop = this.dom.scroller.scrollTop, 30220 iStart = Math.floor( this.pixelsToRow(iScrollTop, false, this.s.ani)+1 ), 30221 iMax = dt.fnRecordsTotal(), 30222 iTotal = dt.fnRecordsDisplay(), 30223 iPossibleEnd = Math.ceil( this.pixelsToRow(iScrollTop+this.s.heights.viewport, false, this.s.ani) ), 30224 iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd, 30225 sStart = dt.fnFormatNumber( iStart ), 30226 sEnd = dt.fnFormatNumber( iEnd ), 30227 sMax = dt.fnFormatNumber( iMax ), 30228 sTotal = dt.fnFormatNumber( iTotal ), 30229 sOut; 30230 30231 if ( dt.fnRecordsDisplay() === 0 && 30232 dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) 30233 { 30234 /* Empty record set */ 30235 sOut = language.sInfoEmpty+ language.sInfoPostFix; 30236 } 30237 else if ( dt.fnRecordsDisplay() === 0 ) 30238 { 30239 /* Empty record set after filtering */ 30240 sOut = language.sInfoEmpty +' '+ 30241 language.sInfoFiltered.replace('_MAX_', sMax)+ 30242 language.sInfoPostFix; 30243 } 30244 else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) 30245 { 30246 /* Normal record set */ 30247 sOut = language.sInfo. 30248 replace('_START_', sStart). 30249 replace('_END_', sEnd). 30250 replace('_MAX_', sMax). 30251 replace('_TOTAL_', sTotal)+ 30252 language.sInfoPostFix; 30253 } 30254 else 30255 { 30256 /* Record set after filtering */ 30257 sOut = language.sInfo. 30258 replace('_START_', sStart). 30259 replace('_END_', sEnd). 30260 replace('_MAX_', sMax). 30261 replace('_TOTAL_', sTotal) +' '+ 30262 language.sInfoFiltered.replace( 30263 '_MAX_', 30264 dt.fnFormatNumber(dt.fnRecordsTotal()) 30265 )+ 30266 language.sInfoPostFix; 30267 } 30268 30269 var callback = language.fnInfoCallback; 30270 if ( callback ) { 30271 sOut = callback.call( dt.oInstance, 30272 dt, iStart, iEnd, iMax, iTotal, sOut 30273 ); 30274 } 30275 30276 var n = dt.aanFeatures.i;
30277 if ( typeof n != 'undefined' ) 30278 { 30279 for ( var i=0, iLen=n.length ; i<iLen ; i++ ) 30280 { 30281 $(n[i]).html( sOut ); 30282 } 30283 } 30284 30285 // DT doesn't actually (yet) trigger this event, but it will in future 30286 $(dt.nTable).triggerHandler( 'info.dt' ); 30287 }, 30288 30289 /** 30290 * Parse CSS height property string as number 30291 * 30292 * An attempt is made to parse the string as a number. Currently supported units are 'px', 30293 * 'vh', and 'rem'. 'em' is partially supported; it works as long as the parent element's 30294 * font size matches the body element. Zero is returned for unrecognized strings. 30295 * @param {string} cssHeight CSS height property string 30296 * @returns {number} height 30297 * @private 30298 */ 30299 _parseHeight: function(cssHeight) { 30300 var height; 30301 var matches = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(px|em|rem|vh)$/.exec(cssHeight); 30302 30303 if (matches === null) { 30304 return 0; 30305 } 30306 30307 var value = parseFloat(matches[1]); 30308 var unit = matches[2]; 30309 30310 if ( unit === 'px' ) { 30311 height = value; 30312 } 30313 else if ( unit === 'vh' ) { 30314 height = ( value / 100 ) * $(window).height(); 30315 } 30316 else if ( unit === 'rem' ) { 30317 height = value * parseFloat($(':root').css('font-size')); 30318 } 30319 else if ( unit === 'em' ) { 30320 height = value * parseFloat($('body').css('font-size')); 30321 } 30322 30323 return height ? 30324 height : 30325 0; 30326 }, 30327 30328 /** 30329 * Scrolling function - fired whenever the scrolling position is changed. 30330 * This method needs to use the stored values to see if the table should be 30331 * redrawn as we are moving towards the end of the information that is 30332 * currently drawn or not. If needed, then it will redraw the table based on 30333 * the new position. 30334 * @returns {void} 30335 * @private 30336 */ 30337 _scroll: function () 30338 { 30339 var 30340 that = this, 30341 heights = this.s.heights, 30342 iScrollTop = this.dom.scroller.scrollTop, 30343 iTopRow; 30344 30345 if ( this.s.skip ) { 30346 return; 30347 } 30348 30349 if ( this.s.ingnoreScroll ) { 30350 return; 30351 } 30352 30353 if ( iScrollTop === this.s.lastScrollTop ) { 30354 return; 30355 } 30356 30357 /* If the table has been sorted or filtered, then we use the redraw that 30358 * DataTables as done, rather than performing our own 30359 */ 30360 if ( this.s.dt.bFiltered || this.s.dt.bSorted ) { 30361 this.s.lastScrollTop = 0; 30362 return; 30363 } 30364 30365 /* Update the table's information display for what is now in the viewport */ 30366 this._info(); 30367 30368 /* We don't want to state save on every scroll event - that's heavy 30369 * handed, so use a timeout to update the state saving only when the 30370 * scrolling has finished 30371 */ 30372 clearTimeout( this.s.stateTO ); 30373 this.s.stateTO = setTimeout( function () { 30374 that.s.dtApi.state.save(); 30375 }, 250 ); 30376 30377 this.s.scrollType = Math.abs(iScrollTop - this.s.lastScrollTop) > heights.viewport ? 30378 'jump' : 30379 'cont'; 30380 30381 this.s.topRowFloat = this.s.scrollType === 'cont' ? 30382 this.pixelsToRow( iScrollTop, false, false ) : 30383 this._domain( 'physicalToVirtual', iScrollTop ) / heights.row; 30384 30385 if ( this.s.topRowFloat < 0 ) { 30386 this.s.topRowFloat = 0; 30387 } 30388 30389 /* Check if the scroll point is outside the trigger boundary which would required 30390 * a DataTables redraw 30391 */ 30392 if ( this.s.forceReposition || iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom ) { 30393 var preRows = Math.ceil( ((this.s.displayBuffer-1)/2) * this.s.viewportRows ); 30394 30395 iTopRow = parseInt(this.s.topRowFloat, 10) - preRows; 30396 this.s.forceReposition = false; 30397 30398 if ( iTopRow <= 0 ) { 30399 /* At the start of the table */ 30400 iTopRow = 0; 30401 } 30402 else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() ) { 30403 /* At the end of the table */ 30404 iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength; 30405 if ( iTopRow < 0 ) { 30406 iTopRow = 0; 30407 } 30408 } 30409 else if ( iTopRow % 2 !== 0 ) { 30410 // For the row-striping classes (odd/even) we want only to start 30411 // on evens otherwise the stripes will change between draws and 30412 // look rubbish 30413 iTopRow++; 30414 } 30415 30416 // Store calcuated value, in case the following condition is not met, but so 30417 // that the draw function will still use it. 30418 this.s.targetTop = iTopRow; 30419 30420 if ( iTopRow != this.s.dt._iDisplayStart ) { 30421 /* Cache the new table position for quick lookups */ 30422 this.s.tableTop = $(this.s.dt.nTable).offset().top; 30423 this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop; 30424 30425 var draw = function () { 30426 that.s.dt._iDisplayStart = that.s.targetTop; 30427 that.s.dt.oApi._fnDraw( that.s.dt ); 30428 }; 30429 30430 /* Do the DataTables redraw based on the calculated start point - note that when 30431 * using server-side processing we introduce a small delay to not DoS the server... 30432 */ 30433 if ( this.s.dt.oFeatures.bServerSide ) { 30434 this.s.forceReposition = true; 30435 30436 clearTimeout( this.s.drawTO ); 30437 this.s.drawTO = setTimeout( draw, this.s.serverWait ); 30438 } 30439 else { 30440 draw(); 30441 } 30442 30443 if ( this.dom.loader && ! this.s.loaderVisible ) { 30444 this.dom.loader.css( 'display', 'block' ); 30445 this.s.loaderVisible = true; 30446 } 30447 } 30448 } 30449 else { 30450 this.s.topRowFloat = this.pixelsToRow( iScrollTop, false, true ); 30451 } 30452 30453 this.s.lastScrollTop = iScrollTop; 30454 this.s.stateSaveThrottle(); 30455 30456 if ( this.s.scrollType === 'jump' && this.s.mousedown ) { 30457 this.s.labelVisible = true; 30458 } 30459 if (this.s.labelVisible) { 30460 var labelFactor = (heights.viewport-heights.labelHeight - heights.xbar) / heights.scroll; 30461 30462 this.dom.label 30463 .html( this.s.dt.fnFormatNumber( parseInt( this.s.topRowFloat, 10 )+1 ) )
30464 .css( 'top', iScrollTop + (iScrollTop * labelFactor) ) 30465 .css( 'right', 10 - this.dom.scroller.scrollLeft) 30466 .css( 'display', 'block' ); 30467 } 30468 }, 30469 30470 /** 30471 * Force the scrolling container to have height beyond that of just the 30472 * table that has been drawn so the user can scroll the whole data set. 30473 * 30474 * Note that if the calculated required scrolling height exceeds a maximum 30475 * value (1 million pixels - hard-coded) the forcing element will be set 30476 * only to that maximum value and virtual / physical domain transforms will 30477 * be used to allow Scroller to display tables of any number of records. 30478 * @returns {void} 30479 * @private 30480 */ 30481 _scrollForce: function () 30482 { 30483 var heights = this.s.heights; 30484 var max = 1000000; 30485 30486 heights.virtual = heights.row * this.s.dt.fnRecordsDisplay(); 30487 heights.scroll = heights.virtual; 30488 30489 if ( heights.scroll > max ) { 30490 heights.scroll = max; 30491 } 30492 30493 // Minimum height so there is always a row visible (the 'no rows found' 30494 // if reduced to zero filtering) 30495 this.dom.force.style.height = heights.scroll > this.s.heights.row ? 30496 heights.scroll+'px' : 30497 this.s.heights.row+'px'; 30498 } 30499} ); 30500 30501 30502 30503/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 30504 * Statics 30505 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 30506 30507 30508/** 30509 * Scroller default settings for initialisation 30510 * @namespace 30511 * @name Scroller.defaults 30512 * @static 30513 */ 30514Scroller.defaults = { 30515 /** 30516 * Scroller uses the boundary scaling factor to decide when to redraw the table - which it 30517 * typically does before you reach the end of the currently loaded data set (in order to 30518 * allow the data to look continuous to a user scrolling through the data). If given as 0 30519 * then the table will be redrawn whenever the viewport is scrolled, while 1 would not 30520 * redraw the table until the currently loaded data has all been shown. You will want 30521 * something in the middle - the default factor of 0.5 is usually suitable. 30522 * @type float 30523 * @default 0.5 30524 * @static 30525 */ 30526 boundaryScale: 0.5, 30527 30528 /** 30529 * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch 30530 * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch 30531 * rows that will be shown in "near scrolling" (i.e. just beyond the current display area). 30532 * The value is based upon the number of rows that can be displayed in the viewport (i.e. 30533 * a value of 1), and will apply the display range to records before before and after the 30534 * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth 30535 * of rows before the current viewport, the current viewport's rows and 1 viewport's worth 30536 * of rows after the current viewport. Adjusting this value can be useful for ensuring 30537 * smooth scrolling based on your data set. 30538 * @type int 30539 * @default 7 30540 * @static 30541 */ 30542 displayBuffer: 9, 30543 30544 /** 30545 * Show (or not) the loading element in the background of the table. Note that you should 30546 * include the dataTables.scroller.css file for this to be displayed correctly. 30547 * @type boolean 30548 * @default false 30549 * @static 30550 */ 30551 loadingIndicator: false, 30552 30553 /** 30554 * Scroller will attempt to automatically calculate the height of rows for it's internal 30555 * calculations. However the height that is used can be overridden using this parameter. 30556 * @type int|string 30557 * @default auto 30558 * @static 30559 */ 30560 rowHeight: "auto", 30561 30562 /** 30563 * When using server-side processing, Scroller will wait a small amount of time to allow 30564 * the scrolling to finish before requesting more data from the server. This prevents 30565 * you from DoSing your own server! The wait time can be configured by this parameter. 30566 * @type int 30567 * @default 200 30568 * @static 30569 */ 30570 serverWait: 200 30571}; 30572 30573Scroller.oDefaults = Scroller.defaults; 30574 30575 30576 30577/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 30578 * Constants 30579 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 30580 30581/** 30582 * Scroller version 30583 * @type String 30584 * @default See code 30585 * @name Scroller.version 30586 * @static 30587 */ 30588Scroller.version = "2.1.1"; 30589 30590 30591 30592/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 30593 * Initialisation 30594 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 30595 30596// Attach a listener to the document which listens for DataTables initialisation 30597// events so we can automatically initialise 30598$(document).on( 'preInit.dt.dtscroller', function (e, settings) { 30599 if ( e.namespace !== 'dt' ) { 30600 return; 30601 } 30602 30603 var init = settings.oInit.scroller; 30604 var defaults = DataTable.defaults.scroller; 30605 30606 if ( init || defaults ) { 30607 var opts = $.extend( {}, init, defaults ); 30608 30609 if ( init !== false ) { 30610 new Scroller( settings, opts ); 30611 } 30612 } 30613} ); 30614 30615
30616// Attach Scroller to DataTables so it can be accessed as an 'extra' 30617$.fn.dataTable.Scroller = Scroller; 30618$.fn.DataTable.Scroller = Scroller; 30619 30620 30621// DataTables 1.10 API method aliases 30622var Api = $.fn.dataTable.Api; 30623 30624Api.register( 'scroller()', function () { 30625 return this; 30626} ); 30627 30628// Undocumented and deprecated - is it actually useful at all? 30629Api.register( 'scroller().rowToPixels()', function ( rowIdx, intParse, virtual ) { 30630 var ctx = this.context; 30631 30632 if ( ctx.length && ctx[0].oScroller ) { 30633 return ctx[0].oScroller.rowToPixels( rowIdx, intParse, virtual ); 30634 } 30635 // undefined 30636} ); 30637 30638// Undocumented and deprecated - is it actually useful at all? 30639Api.register( 'scroller().pixelsToRow()', function ( pixels, intParse, virtual ) { 30640 var ctx = this.context; 30641 30642 if ( ctx.length && ctx[0].oScroller ) { 30643 return ctx[0].oScroller.pixelsToRow( pixels, intParse, virtual ); 30644 } 30645 // undefined 30646} ); 30647 30648// `scroller().scrollToRow()` is undocumented and deprecated. Use `scroller.toPosition() 30649Api.register( ['scroller().scrollToRow()', 'scroller.toPosition()'], function ( idx, ani ) { 30650 this.iterator( 'table', function ( ctx ) { 30651 if ( ctx.oScroller ) { 30652 ctx.oScroller.scrollToRow( idx, ani ); 30653 } 30654 } ); 30655 30656 return this; 30657} ); 30658 30659Api.register( 'row().scrollTo()', function ( ani ) { 30660 var that = this; 30661 30662 this.iterator( 'row', function ( ctx, rowIdx ) { 30663 if ( ctx.oScroller ) { 30664 var displayIdx = that 30665 .rows( { order: 'applied', search: 'applied' } ) 30666 .indexes() 30667 .indexOf( rowIdx ); 30668 30669 ctx.oScroller.scrollToRow( displayIdx, ani ); 30670 } 30671 } ); 30672 30673 return this; 30674} ); 30675 30676Api.register( 'scroller.measure()', function ( redraw ) { 30677 this.iterator( 'table', function ( ctx ) { 30678 if ( ctx.oScroller ) { 30679 ctx.oScroller.measure( redraw ); 30680 } 30681 } ); 30682 30683 return this; 30684} ); 30685 30686Api.register( 'scroller.page()', function() { 30687 var ctx = this.context; 30688 30689 if ( ctx.length && ctx[0].oScroller ) { 30690 return ctx[0].oScroller.pageInfo(); 30691 } 30692 // undefined 30693} ); 30694 30695 30696return DataTable; 30697})); 30698 30699 30700/*! SearchBuilder 1.4.2 30701 * ©SpryMedia Ltd - datatables.net/license/mit 30702 */ 30703 30704(function( factory ){ 30705 if ( typeof define === 'function' && define.amd ) { 30706 // AMD 30707 define( ['jquery', 'datatables.net'], function ( $ ) { 30708 return factory( $, window, document ); 30709 } ); 30710 } 30711 else if ( typeof exports === 'object' ) { 30712 // CommonJS 30713 var jq = require('jquery'); 30714 var cjsRequires = function (root, $) { 30715 if ( ! $.fn.dataTable ) { 30716 require('datatables.net')(root, $); 30717 } 30718 }; 30719 30720 if (typeof window !== 'undefined') { 30721 module.exports = function (root, $) { 30722 if ( ! root ) { 30723 // CommonJS environments without a window global must pass a 30724 // root. This will give an error otherwise 30725 root = window; 30726 } 30727 30728 if ( ! $ ) { 30729 $ = jq( root ); 30730 } 30731 30732 cjsRequires( root, $ ); 30733 return factory( $, root, root.document ); 30734 }; 30735 } 30736 else { 30737 cjsRequires( window, jq ); 30738 module.exports = factory( jq, window, window.document ); 30739 } 30740 } 30741 else { 30742 // Browser 30743 factory( jQuery, window, document ); 30744 } 30745}(function( $, window, document, undefined ) { 30746'use strict'; 30747var DataTable = $.fn.dataTable; 30748 30749 30750(function () { 30751 'use strict'; 30752 30753 var $$3; 30754 var dataTable$3; 30755 function moment() { 30756 return window.moment; 30757 } 30758 function luxon() { 30759 return window.luxon; 30760 } 30761 /** 30762 * Sets the value of jQuery for use in the file 30763 * 30764 * @param jq the instance of jQuery to be set 30765 */ 30766 function setJQuery$2(jq) { 30767 $$3 = jq; 30768 dataTable$3 = jq.fn.dataTable; 30769 } 30770 /** 30771 * The Criteria class is used within SearchBuilder to represent a search criteria 30772 */ 30773 var Criteria = /** @class */ (function () { 30774 function Criteria(table, opts, topGroup, index, depth, serverData) { 30775 if (index === void 0) { index = 0; } 30776 if (depth === void 0) { depth = 1; } 30777 if (serverData === void 0) { serverData = undefined; } 30778 var _this = this; 30779 // Check that the required version of DataTables is included 30780 if (!dataTable$3 || !dataTable$3.versionCheck || !dataTable$3.versionCheck('1.10.0')) { 30781 throw new Error('SearchPane requires DataTables 1.10 or newer'); 30782 } 30783 this.classes = $$3.extend(true, {}, Criteria.classes); 30784 // Get options from user and any extra conditions/column types defined by plug-ins 30785 this.c = $$3.extend(true, {}, Criteria.defaults, $$3.fn.dataTable.ext.searchBuilder, opts); 30786 var i18n = this.c.i18n; 30787 this.s = { 30788 condition: undefined, 30789 conditions: {}, 30790 data: undefined, 30791 dataIdx: -1, 30792 dataPoints: [], 30793 dateFormat: false, 30794 depth: depth, 30795 dt: table, 30796 filled: false, 30797 index: index, 30798 origData: undefined, 30799 preventRedraw: false, 30800 serverData: serverData, 30801 topGroup: topGroup, 30802 type: '', 30803 value: [] 30804 }; 30805 this.dom = { 30806 buttons: $$3('<div/>') 30807 .addClass(this.classes.buttonContainer), 30808 condition: $$3('<select disabled/>') 30809 .addClass(this.classes.condition) 30810 .addClass(this.classes.dropDown) 30811 .addClass(this.classes.italic) 30812 .attr('autocomplete', 'hacking'), 30813 conditionTitle: $$3('<option value="" disabled selected hidden/>') 30814 .html(this.s.dt.i18n('searchBuilder.condition', i18n.condition)), 30815 container: $$3('<div/>') 30816 .addClass(this.classes.container), 30817 data: $$3('<select/>') 30818 .addClass(this.classes.data) 30819 .addClass(this.classes.dropDown) 30820 .addClass(this.classes.italic), 30821 dataTitle: $$3('<option value="" disabled selected hidden/>') 30822 .html(this.s.dt.i18n('searchBuilder.data', i18n.data)), 30823 defaultValue: $$3('<select disabled/>') 30824 .addClass(this.classes.value) 30825 .addClass(this.classes.dropDown) 30826 .addClass(this.classes.select) 30827 .addClass(this.classes.italic), 30828 "delete": $$3('<button/>') 30829 .html(this.s.dt.i18n('searchBuilder.delete', i18n["delete"])) 30830 .addClass(this.classes["delete"]) 30831 .addClass(this.classes.button) 30832 .attr('title', this.s.dt.i18n('searchBuilder.deleteTitle', i18n.deleteTitle)) 30833 .attr('type', 'button'), 30834 inputCont: $$3('<div/>') 30835 .addClass(this.classes.inputCont), 30836 // eslint-disable-next-line no-useless-escape 30837 left: $$3('<button/>') 30838 .html(this.s.dt.i18n('searchBuilder.left', i18n.left)) 30839 .addClass(this.classes.left) 30840 .addClass(this.classes.button) 30841 .attr('title', this.s.dt.i18n('searchBuilder.leftTitle', i18n.leftTitle)) 30842 .attr('type', 'button'), 30843 // eslint-disable-next-line no-useless-escape 30844 right: $$3('<button/>') 30845 .html(this.s.dt.i18n('searchBuilder.right', i18n.right)) 30846 .addClass(this.classes.right) 30847 .addClass(this.classes.button) 30848 .attr('title', this.s.dt.i18n('searchBuilder.rightTitle', i18n.rightTitle)) 30849 .attr('type', 'button'), 30850 value: [ 30851 $$3('<select disabled/>') 30852 .addClass(this.classes.value) 30853 .addClass(this.classes.dropDown) 30854 .addClass(this.classes.italic) 30855 .addClass(this.classes.select) 30856 ], 30857 valueTitle: $$3('<option value="--valueTitle--" disabled selected hidden/>') 30858 .html(this.s.dt.i18n('searchBuilder.value', i18n.value)) 30859 }; 30860 // If the greyscale option is selected then add the class to add the grey colour to SearchBuilder 30861 if (this.c.greyscale) { 30862 this.dom.data.addClass(this.classes.greyscale); 30863 this.dom.condition.addClass(this.classes.greyscale); 30864 this.dom.defaultValue.addClass(this.classes.greyscale); 30865 for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { 30866 var val = _a[_i]; 30867 val.addClass(this.classes.greyscale); 30868 } 30869 } 30870 $$3(window).on('resize.dtsb', dataTable$3.util.throttle(function () { 30871 _this.s.topGroup.trigger('dtsb-redrawLogic'); 30872 })); 30873 this._buildCriteria(); 30874 return this; 30875 } 30876 /** 30877 * Escape html characters within a string 30878 * 30879 * @param txt the string to be escaped 30880 * @returns the escaped string 30881 */ 30882 Criteria._escapeHTML = function (txt) { 30883 return txt 30884 .toString() 30885 .replace(/&/g, '&') 30886 .replace(/</g, '<') 30887 .replace(/>/g, '>') 30888 .replace(/"/g, '"'); 30889 }; 30890 /** 30891 * Parses formatted numbers down to a form where they can be compared 30892 * 30893 * @param val the value to convert 30894 * @returns the converted value 30895 */ 30896 Criteria.parseNumFmt = function (val) { 30897 return +val.replace(/(?!^-)[^0-9.]/g, ''); 30898 }; 30899 /** 30900 * Adds the left button to the criteria 30901 */ 30902 Criteria.prototype.updateArrows = function (hasSiblings) { 30903 if (hasSiblings === void 0) { hasSiblings = false; } 30904 // Empty the container and append all of the elements in the correct order 30905 this.dom.container.children().detach(); 30906 this.dom.container 30907 .append(this.dom.data) 30908 .append(this.dom.condition) 30909 .append(this.dom.inputCont); 30910 this.setListeners(); 30911 // Trigger the inserted events for the value elements as they are inserted 30912 if (this.dom.value[0] !== undefined) { 30913 this.dom.value[0].trigger('dtsb-inserted'); 30914 } 30915 for (var i = 1; i < this.dom.value.length; i++) { 30916 this.dom.inputCont.append(this.dom.value[i]); 30917 this.dom.value[i].trigger('dtsb-inserted'); 30918 } 30919 // If this is a top level criteria then don't let it move left 30920 if (this.s.depth > 1) { 30921 this.dom.buttons.append(this.dom.left); 30922 } 30923 // If the depthLimit of the query has been hit then don't add the right button 30924 if ((this.c.depthLimit === false || this.s.depth < this.c.depthLimit) && hasSiblings) { 30925 this.dom.buttons.append(this.dom.right); 30926 } 30927 else { 30928 this.dom.right.remove(); 30929 } 30930 this.dom.buttons.append(this.dom["delete"]); 30931 this.dom.container.append(this.dom.buttons); 30932 }; 30933 /** 30934 * Destroys the criteria, removing listeners and container from the dom 30935 */ 30936 Criteria.prototype.destroy = function () { 30937 // Turn off listeners 30938 this.dom.data.off('.dtsb'); 30939 this.dom.condition.off('.dtsb'); 30940 this.dom["delete"].off('.dtsb'); 30941 for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { 30942 var val = _a[_i]; 30943 val.off('.dtsb'); 30944 }
30945 // Remove container from the dom 30946 this.dom.container.remove(); 30947 }; 30948 /** 30949 * Passes in the data for the row and compares it against this single criteria 30950 * 30951 * @param rowData The data for the row to be compared 30952 * @returns boolean Whether the criteria has passed 30953 */ 30954 Criteria.prototype.search = function (rowData, rowIdx) { 30955 var condition = this.s.conditions[this.s.condition]; 30956 if (this.s.condition !== undefined && condition !== undefined) { 30957 var filter = rowData[this.s.dataIdx]; 30958 // This check is in place for if a custom decimal character is in place 30959 if (this.s.type.includes('num') && 30960 (this.s.dt.settings()[0].oLanguage.sDecimal !== '' || 30961 this.s.dt.settings()[0].oLanguage.sThousands !== '')) { 30962 var splitRD = [rowData[this.s.dataIdx]]; 30963 if (this.s.dt.settings()[0].oLanguage.sDecimal !== '') { 30964 splitRD = rowData[this.s.dataIdx].split(this.s.dt.settings()[0].oLanguage.sDecimal); 30965 } 30966 if (this.s.dt.settings()[0].oLanguage.sThousands !== '') { 30967 for (var i = 0; i < splitRD.length; i++) { 30968 splitRD[i] = splitRD[i].replace(this.s.dt.settings()[0].oLanguage.sThousands, ','); 30969 } 30970 } 30971 filter = splitRD.join('.'); 30972 } 30973 // If orthogonal data is in place we need to get it's values for searching 30974 if (this.c.orthogonal.search !== 'filter') { 30975 var settings = this.s.dt.settings()[0]; 30976 filter = settings.oApi._fnGetCellData(settings, rowIdx, this.s.dataIdx, typeof this.c.orthogonal === 'string' ? 30977 this.c.orthogonal : 30978 this.c.orthogonal.search); 30979 } 30980 if (this.s.type === 'array') { 30981 // Make sure we are working with an array 30982 if (!Array.isArray(filter)) { 30983 filter = [filter]; 30984 } 30985 filter.sort(); 30986 for (var _i = 0, filter_1 = filter; _i < filter_1.length; _i++) { 30987 var filt = filter_1[_i]; 30988 if (filt && typeof filt === 'string') { 30989 filt = filt.replace(/[\r\n\u2028]/g, ' '); 30990 } 30991 } 30992 } 30993 else if (filter !== null && typeof filter === 'string') { 30994 filter = filter.replace(/[\r\n\u2028]/g, ' '); 30995 } 30996 if (this.s.type.includes('html') && typeof filter === 'string') { 30997 filter = filter.replace(/(<([^>]+)>)/ig, ''); 30998 } 30999 // Not ideal, but jqueries .val() returns an empty string even 31000 // when the value set is null, so we shall assume the two are equal 31001 if (filter === null) { 31002 filter = ''; 31003 } 31004 return condition.search(filter, this.s.value, this); 31005 } 31006 }; 31007 /** 31008 * Gets the details required to rebuild the criteria 31009 */ 31010 Criteria.prototype.getDetails = function (deFormatDates) { 31011 if (deFormatDates === void 0) { deFormatDates = false; } 31012 // This check is in place for if a custom decimal character is in place 31013 if (this.s.type !== null && 31014 this.s.type.includes('num') && 31015 (this.s.dt.settings()[0].oLanguage.sDecimal !== '' || this.s.dt.settings()[0].oLanguage.sThousands !== '')) { 31016 for (var i = 0; i < this.s.value.length; i++) { 31017 var splitRD = [this.s.value[i].toString()]; 31018 if (this.s.dt.settings()[0].oLanguage.sDecimal !== '') { 31019 splitRD = this.s.value[i].split(this.s.dt.settings()[0].oLanguage.sDecimal); 31020 } 31021 if (this.s.dt.settings()[0].oLanguage.sThousands !== '') { 31022 for (var j = 0; j < splitRD.length; j++) { 31023 splitRD[j] = splitRD[j].replace(this.s.dt.settings()[0].oLanguage.sThousands, ','); 31024 } 31025 } 31026 this.s.value[i] = splitRD.join('.'); 31027 } 31028 } 31029 else if (this.s.type !== null && deFormatDates) { 31030 if (this.s.type.includes('date') || 31031 this.s.type.includes('time')) { 31032 for (var i = 0; i < this.s.value.length; i++) { 31033 if (this.s.value[i].match(/^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$/g) === null) { 31034 this.s.value[i] = ''; 31035 } 31036 } 31037 } 31038 else if (this.s.type.includes('moment')) { 31039 for (var i = 0; i < this.s.value.length; i++) { 31040 if (this.s.value[i] && 31041 this.s.value[i].length > 0 && 31042 moment()(this.s.value[i], this.s.dateFormat, true).isValid()) { 31043 this.s.value[i] = moment()(this.s.value[i], this.s.dateFormat).format('YYYY-MM-DD HH:mm:ss'); 31044 } 31045 } 31046 } 31047 else if (this.s.type.includes('luxon')) { 31048 for (var i = 0; i < this.s.value.length; i++) { 31049 if (this.s.value[i] && 31050 this.s.value[i].length > 0 && 31051 luxon().DateTime.fromFormat(this.s.value[i], this.s.dateFormat).invalid === null) { 31052 this.s.value[i] = luxon().DateTime.fromFormat(this.s.value[i], this.s.dateFormat).toFormat('yyyy-MM-dd HH:mm:ss'); 31053 } 31054 } 31055 } 31056 } 31057 if (this.s.type.includes('num') && this.s.dt.page.info().serverSide) { 31058 for (var i = 0; i < this.s.value.length; i++) { 31059 this.s.value[i] = this.s.value[i].replace(/[^0-9.\-]/g, ''); 31060 } 31061 } 31062 return { 31063 condition: this.s.condition, 31064 data: this.s.data, 31065 origData: this.s.origData, 31066 type: this.s.type, 31067 value: this.s.value.map(function (a) { return a !== null && a !== undefined ? a.toString() : a; }) 31068 }; 31069 }; 31070 /** 31071 * Getter for the node for the container of the criteria 31072 * 31073 * @returns JQuery<HTMLElement> the node for the container 31074 */ 31075 Criteria.prototype.getNode = function () { 31076 return this.dom.container; 31077 }; 31078 /** 31079 * Populates the criteria data, condition and value(s) as far as has been selected 31080 */ 31081 Criteria.prototype.populate = function () { 31082 this._populateData(); 31083 // If the column index has been found attempt to select a condition 31084 if (this.s.dataIdx !== -1) { 31085 this._populateCondition(); 31086 // If the condittion has been found attempt to select the values 31087 if (this.s.condition !== undefined) { 31088 this._populateValue(); 31089 } 31090 } 31091 }; 31092 /** 31093 * Rebuilds the criteria based upon the details passed in 31094 * 31095 * @param loadedCriteria the details required to rebuild the criteria 31096 */ 31097 Criteria.prototype.rebuild = function (loadedCriteria) { 31098 // Check to see if the previously selected data exists, if so select it 31099 var foundData = false; 31100 var dataIdx; 31101 this._populateData(); 31102 // If a data selection has previously been made attempt to find and select it 31103 if (loadedCriteria.data !== undefined) { 31104 var italic_1 = this.classes.italic; 31105 var data_1 = this.dom.data; 31106 this.dom.data.children('option').each(function () { 31107 if (!foundData && 31108 ($$3(this).text() === loadedCriteria.data || 31109 loadedCriteria.origData && $$3(this).prop('origData') === loadedCriteria.origData)) { 31110 $$3(this).prop('selected', true); 31111 data_1.removeClass(italic_1); 31112 foundData = true; 31113 dataIdx = $$3(this).val(); 31114 } 31115 else { 31116 $$3(this).removeProp('selected'); 31117 } 31118 }); 31119 } 31120 // If the data has been found and selected then the condition can be populated and searched 31121 if (foundData) { 31122 this.s.data = loadedCriteria.data; 31123 this.s.origData = loadedCriteria.origData; 31124 this.s.dataIdx = dataIdx; 31125 this.c.orthogonal = this._getOptions().orthogonal; 31126 this.dom.dataTitle.remove(); 31127 this._populateCondition(); 31128 this.dom.conditionTitle.remove(); 31129 var condition = void 0; 31130 // Check to see if the previously selected condition exists, if so select it 31131 var options = this.dom.condition.children('option'); 31132 // eslint-disable-next-line @typescript-eslint/prefer-for-of 31133 for (var i = 0; i < options.length; i++) { 31134 var option = $$3(options[i]); 31135 if (loadedCriteria.condition !== undefined && 31136 option.val() === loadedCriteria.condition && 31137 typeof loadedCriteria.condition === 'string') { 31138 option.prop('selected', true); 31139 condition = option.val(); 31140 } 31141 else { 31142 option.removeProp('selected'); 31143 } 31144 } 31145 this.s.condition = condition; 31146 // If the condition has been found and selected then the value can be populated and searched 31147 if (this.s.condition !== undefined) { 31148 this.dom.conditionTitle.removeProp('selected'); 31149 this.dom.conditionTitle.remove(); 31150 this.dom.condition.removeClass(this.classes.italic); 31151 // eslint-disable-next-line @typescript-eslint/prefer-for-of 31152 for (var i = 0; i < options.length; i++) { 31153 var option = $$3(options[i]); 31154 if (option.val() !== this.s.condition) { 31155 option.removeProp('selected'); 31156 } 31157 } 31158 this._populateValue(loadedCriteria); 31159 } 31160 else { 31161 this.dom.conditionTitle.prependTo(this.dom.condition).prop('selected', true); 31162 } 31163 } 31164 }; 31165 /** 31166 * Sets the listeners for the criteria 31167 */ 31168 Criteria.prototype.setListeners = function () { 31169 var _this = this; 31170 this.dom.data 31171 .unbind('change') 31172 .on('change.dtsb', function () { 31173 _this.dom.dataTitle.removeProp('selected');
31174 // Need to go over every option to identify the correct selection 31175 var options = _this.dom.data.children('option.' + _this.classes.option); 31176 // eslint-disable-next-line @typescript-eslint/prefer-for-of 31177 for (var i = 0; i < options.length; i++) { 31178 var option = $$3(options[i]); 31179 if (option.val() === _this.dom.data.val()) { 31180 _this.dom.data.removeClass(_this.classes.italic); 31181 option.prop('selected', true); 31182 _this.s.dataIdx = +option.val(); 31183 _this.s.data = option.text(); 31184 _this.s.origData = option.prop('origData'); 31185 _this.c.orthogonal = _this._getOptions().orthogonal; 31186 // When the data is changed, the values in condition and 31187 // value may also change so need to renew them 31188 _this._clearCondition(); 31189 _this._clearValue(); 31190 _this._populateCondition(); 31191 // If this criteria was previously active in the search then 31192 // remove it from the search and trigger a new search 31193 if (_this.s.filled) { 31194 _this.s.filled = false; 31195 _this.s.dt.draw(); 31196 _this.setListeners(); 31197 } 31198 _this.s.dt.state.save(); 31199 } 31200 else { 31201 option.removeProp('selected'); 31202 } 31203 } 31204 }); 31205 this.dom.condition 31206 .unbind('change') 31207 .on('change.dtsb', function () { 31208 _this.dom.conditionTitle.removeProp('selected'); 31209 // Need to go over every option to identify the correct selection 31210 var options = _this.dom.condition.children('option.' + _this.classes.option); 31211 // eslint-disable-next-line @typescript-eslint/prefer-for-of 31212 for (var i = 0; i < options.length; i++) { 31213 var option = $$3(options[i]); 31214 if (option.val() === _this.dom.condition.val()) { 31215 _this.dom.condition.removeClass(_this.classes.italic); 31216 option.prop('selected', true); 31217 var condDisp = option.val(); 31218 // Find the condition that has been selected and store it internally 31219 for (var _i = 0, _a = Object.keys(_this.s.conditions); _i < _a.length; _i++) { 31220 var cond = _a[_i]; 31221 if (cond === condDisp) { 31222 _this.s.condition = condDisp; 31223 break; 31224 } 31225 } 31226 // When the condition is changed, the value selector may switch between 31227 // a select element and an input element 31228 _this._clearValue(); 31229 _this._populateValue(); 31230 for (var _b = 0, _c = _this.dom.value; _b < _c.length; _b++) { 31231 var val = _c[_b]; 31232 // If this criteria was previously active in the search then remove 31233 // it from the search and trigger a new search 31234 if (_this.s.filled && val !== undefined && _this.dom.inputCont.has(val[0]).length !== 0) { 31235 _this.s.filled = false; 31236 _this.s.dt.draw(); 31237 _this.setListeners(); 31238 } 31239 } 31240 if (_this.dom.value.length === 0 || 31241 _this.dom.value.length === 1 && _this.dom.value[0] === undefined) { 31242 _this.s.dt.draw(); 31243 } 31244 } 31245 else { 31246 option.removeProp('selected'); 31247 } 31248 } 31249 }); 31250 }; 31251 Criteria.prototype.setupButtons = function () { 31252 if (window.innerWidth > 550) { 31253 this.dom.container.removeClass(this.classes.vertical); 31254 this.dom.buttons.css('left', null); 31255 this.dom.buttons.css('top', null); 31256 return; 31257 } 31258 this.dom.container.addClass(this.classes.vertical); 31259 this.dom.buttons.css('left', this.dom.data.innerWidth()); 31260 this.dom.buttons.css('top', this.dom.data.position().top); 31261 }; 31262 /** 31263 * Builds the elements of the dom together 31264 */ 31265 Criteria.prototype._buildCriteria = function () { 31266 // Append Titles for select elements 31267 this.dom.data.append(this.dom.dataTitle); 31268 this.dom.condition.append(this.dom.conditionTitle); 31269 // Add elements to container 31270 this.dom.container 31271 .append(this.dom.data) 31272 .append(this.dom.condition); 31273 this.dom.inputCont.empty(); 31274 for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { 31275 var val = _a[_i]; 31276 val.append(this.dom.valueTitle); 31277 this.dom.inputCont.append(val); 31278 } 31279 // Add buttons to container 31280 this.dom.buttons 31281 .append(this.dom["delete"]) 31282 .append(this.dom.right); 31283 this.dom.container.append(this.dom.inputCont).append(this.dom.buttons); 31284 this.setListeners(); 31285 }; 31286 /** 31287 * Clears the condition select element 31288 */ 31289 Criteria.prototype._clearCondition = function () { 31290 this.dom.condition.empty(); 31291 this.dom.conditionTitle.prop('selected', true).attr('disabled', 'true'); 31292 this.dom.condition.prepend(this.dom.conditionTitle).prop('selectedIndex', 0); 31293 this.s.conditions = {}; 31294 this.s.condition = undefined; 31295 }; 31296 /** 31297 * Clears the value elements 31298 */ 31299 Criteria.prototype._clearValue = function () { 31300 if (this.s.condition !== undefined) { 31301 if (this.dom.value.length > 0 && this.dom.value[0] !== undefined) { 31302 var _loop_1 = function (val) { 31303 if (val !== undefined) { 31304 // Timeout is annoying but because of IOS 31305 setTimeout(function () { 31306 val.remove(); 31307 }, 50); 31308 } 31309 }; 31310 // Remove all of the value elements 31311 for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { 31312 var val = _a[_i]; 31313 _loop_1(val); 31314 } 31315 } 31316 // Call the init function to get the value elements for this condition 31317 this.dom.value = [].concat(this.s.conditions[this.s.condition].init(this, Criteria.updateListener)); 31318 if (this.dom.value.length > 0 && this.dom.value[0] !== undefined) { 31319 this.dom.inputCont 31320 .empty() 31321 .append(this.dom.value[0]) 31322 .insertAfter(this.dom.condition); 31323 this.dom.value[0].trigger('dtsb-inserted'); 31324 // Insert all of the value elements 31325 for (var i = 1; i < this.dom.value.length; i++) { 31326 this.dom.inputCont.append(this.dom.value[i]); 31327 this.dom.value[i].trigger('dtsb-inserted'); 31328 } 31329 } 31330 } 31331 else { 31332 var _loop_2 = function (val) { 31333 if (val !== undefined) { 31334 // Timeout is annoying but because of IOS 31335 setTimeout(function () { 31336 val.remove(); 31337 }, 50); 31338 } 31339 }; 31340 // Remove all of the value elements 31341 for (var _b = 0, _c = this.dom.value; _b < _c.length; _b++) { 31342 var val = _c[_b]; 31343 _loop_2(val); 31344 } 31345 // Append the default valueTitle to the default select element 31346 this.dom.valueTitle 31347 .prop('selected', true); 31348 this.dom.defaultValue 31349 .append(this.dom.valueTitle) 31350 .insertAfter(this.dom.condition); 31351 } 31352 this.s.value = []; 31353 this.dom.value = [ 31354 $$3('<select disabled/>') 31355 .addClass(this.classes.value) 31356 .addClass(this.classes.dropDown) 31357 .addClass(this.classes.italic) 31358 .addClass(this.classes.select) 31359 .append(this.dom.valueTitle.clone()) 31360 ]; 31361 }; 31362 /** 31363 * Gets the options for the column 31364 * 31365 * @returns {object} The options for the column 31366 */ 31367 Criteria.prototype._getOptions = function () { 31368 var table = this.s.dt; 31369 return $$3.extend(true, {}, Criteria.defaults, table.settings()[0].aoColumns[this.s.dataIdx].searchBuilder); 31370 }; 31371 /** 31372 * Populates the condition dropdown 31373 */ 31374 Criteria.prototype._populateCondition = function () { 31375 var conditionOpts = []; 31376 var conditionsLength = Object.keys(this.s.conditions).length; 31377 var colInits = this.s.dt.settings()[0].aoColumns; 31378 var column = +this.dom.data.children('option:selected').val(); 31379 // If there are no conditions stored then we need to get them from the appropriate type 31380 if (conditionsLength === 0) { 31381 this.s.type = this.s.dt.columns().type().toArray()[column]; 31382 if (colInits !== undefined) { 31383 var colInit = colInits[column]; 31384 if (colInit.searchBuilderType !== undefined && colInit.searchBuilderType !== null) { 31385 this.s.type = colInit.searchBuilderType; 31386 } 31387 else if (this.s.type === undefined || this.s.type === null) { 31388 this.s.type = colInit.sType; 31389 } 31390 } 31391 // If the column type is still unknown, call a draw to try reading it again 31392 if (this.s.type === null || this.s.type === undefined) { 31393 $$3.fn.dataTable.ext.oApi._fnColumnTypes(this.s.dt.settings()[0]); 31394 this.s.type = this.s.dt.columns().type().toArray()[column]; 31395 } 31396 // Enable the condition element 31397 this.dom.condition 31398 .removeAttr('disabled') 31399 .empty() 31400 .append(this.dom.conditionTitle) 31401 .addClass(this.classes.italic); 31402 this.dom.conditionTitle 31403 .prop('selected', true); 31404 var decimal = this.s.dt.settings()[0].oLanguage.sDecimal; 31405 // This check is in place for if a custom decimal character is in place 31406 if (decimal !== '' && this.s.type.indexOf(decimal) === this.s.type.length - decimal.length) { 31407 if (this.s.type.includes('num-fmt')) { 31408 this.s.type = this.s.type.replace(decimal, ''); 31409 } 31410 else if (this.s.type.includes('num')) { 31411 this.s.type = this.s.type.replace(decimal, ''); 31412 } 31413 } 31414 // Select which conditions are going to be used based on the column type 31415 var conditionObj = this.c.conditions[this.s.type] !== undefined ? 31416 this.c.conditions[this.s.type] : 31417 this.s.type.includes('moment') ? 31418 this.c.conditions.moment : 31419 this.s.type.includes('luxon') ? 31420 this.c.conditions.luxon : 31421 this.c.conditions.string; 31422 // If it is a moment format then extract the date format 31423 if (this.s.type.includes('moment')) { 31424 this.s.dateFormat = this.s.type.replace(/moment-/g, ''); 31425 } 31426 else if (this.s.type.includes('luxon')) { 31427 this.s.dateFormat = this.s.type.replace(/luxon-/g, ''); 31428 } 31429 // Add all of the conditions to the select element 31430 for (var _i = 0, _a = Object.keys(conditionObj); _i < _a.length; _i++) { 31431 var condition = _a[_i]; 31432 if (conditionObj[condition] !== null) { 31433 // Serverside processing does not supply the options for the select elements 31434 // Instead input elements need to be used for these instead 31435 if (this.s.dt.page.info().serverSide && conditionObj[condition].init === Criteria.initSelect) { 31436 var col = colInits[column]; 31437 if (this.s.serverData && this.s.serverData[col.data]) { 31438 conditionObj[condition].init = Criteria.initSelectSSP; 31439 conditionObj[condition].inputValue = Criteria.inputValueSelect; 31440 conditionObj[condition].isInputValid = Criteria.isInputValidSelect; 31441 } 31442 else { 31443 conditionObj[condition].init = Criteria.initInput; 31444 conditionObj[condition].inputValue = Criteria.inputValueInput; 31445 conditionObj[condition].isInputValid = Criteria.isInputValidInput; 31446 } 31447 } 31448 this.s.conditions[condition] = conditionObj[condition]; 31449 var condName = conditionObj[condition].conditionName; 31450 if (typeof condName === 'function') { 31451 condName = condName(this.s.dt, this.c.i18n); 31452 } 31453 conditionOpts.push($$3('<option>', { 31454 text: condName, 31455 value: condition 31456 }) 31457 .addClass(this.classes.option) 31458 .addClass(this.classes.notItalic)); 31459 } 31460 } 31461 } 31462 // Otherwise we can just load them in 31463 else if (conditionsLength > 0) { 31464 this.dom.condition.empty().removeAttr('disabled').addClass(this.classes.italic); 31465 for (var _b = 0, _c = Object.keys(this.s.conditions); _b < _c.length; _b++) { 31466 var condition = _c[_b]; 31467 var condName = this.s.conditions[condition].conditionName; 31468 if (typeof condName === 'function') { 31469 condName = condName(this.s.dt, this.c.i18n); 31470 } 31471 var newOpt = $$3('<option>', { 31472 text: condName, 31473 value: condition 31474 }) 31475 .addClass(this.classes.option) 31476 .addClass(this.classes.notItalic); 31477 if (this.s.condition !== undefined && this.s.condition === condName) { 31478 newOpt.prop('selected', true); 31479 this.dom.condition.removeClass(this.classes.italic); 31480 } 31481 conditionOpts.push(newOpt); 31482 } 31483 } 31484 else { 31485 this.dom.condition 31486 .attr('disabled', 'true') 31487
31487 .addClass(this.classes.italic); 31488 return; 31489 } 31490 for (var _d = 0, conditionOpts_1 = conditionOpts; _d < conditionOpts_1.length; _d++) { 31491 var opt = conditionOpts_1[_d]; 31492 this.dom.condition.append(opt); 31493 } 31494 // Selecting a default condition if one is set 31495 if (colInits[column].searchBuilder && colInits[column].searchBuilder.defaultCondition) { 31496 var defaultCondition = colInits[column].searchBuilder.defaultCondition; 31497 // If it is a number just use it as an index 31498 if (typeof defaultCondition === 'number') { 31499 this.dom.condition.prop('selectedIndex', defaultCondition); 31500 this.dom.condition.trigger('change'); 31501 } 31502 // If it is a string then things get slightly more tricly 31503 else if (typeof defaultCondition === 'string') { 31504 // We need to check each condition option to see if any will match 31505 for (var i = 0; i < conditionOpts.length; i++) { 31506 // Need to check against the stored conditions so we can match the token "cond" to the option 31507 for (var _e = 0, _f = Object.keys(this.s.conditions); _e < _f.length; _e++) { 31508 var cond = _f[_e]; 31509 var condName = this.s.conditions[cond].conditionName; 31510 if ( 31511 // If the conditionName matches the text of the option 31512 (typeof condName === 'string' ? condName : condName(this.s.dt, this.c.i18n)) === 31513 conditionOpts[i].text() && 31514 // and the tokens match 31515 cond === defaultCondition) { 31516 // Select that option 31517 this.dom.condition 31518 .prop('selectedIndex', this.dom.condition.children().toArray().indexOf(conditionOpts[i][0])) 31519 .removeClass(this.classes.italic); 31520 this.dom.condition.trigger('change'); 31521 i = conditionOpts.length; 31522 break; 31523 } 31524 } 31525 } 31526 } 31527 } 31528 // If not default set then default to 0, the title 31529 else { 31530 this.dom.condition.prop('selectedIndex', 0); 31531 } 31532 }; 31533 /** 31534 * Populates the data / column select element 31535 */ 31536 Criteria.prototype._populateData = function () { 31537 var columns = this.s.dt.settings()[0].aoColumns; 31538 var includeColumns = this.s.dt.columns(this.c.columns).indexes().toArray(); 31539 this.dom.data.empty().append(this.dom.dataTitle); 31540 for (var index = 0; index < columns.length; index++) { 31541 // Need to check that the column can be filtered on before adding it 31542 if (this.c.columns === true || includeColumns.includes(index)) { 31543 var col = columns[index]; 31544 var opt = { 31545 index: index, 31546 origData: col.data, 31547 text: (col.searchBuilderTitle || col.sTitle) 31548 .replace(/(<([^>]+)>)/ig, '') 31549 }; 31550 this.dom.data.append($$3('<option>', { 31551 text: opt.text, 31552 value: opt.index 31553 }) 31554 .addClass(this.classes.option) 31555 .addClass(this.classes.notItalic) 31556 .prop('origData', col.data) 31557 .prop('selected', this.s.dataIdx === opt.index ? true : false)); 31558 if (this.s.dataIdx === opt.index) { 31559 this.dom.dataTitle.removeProp('selected'); 31560 } 31561 } 31562 } 31563 }; 31564 /** 31565 * Populates the Value select element 31566 * 31567 * @param loadedCriteria optional, used to reload criteria from predefined filters 31568 */ 31569 Criteria.prototype._populateValue = function (loadedCriteria) { 31570 var _this = this; 31571 var prevFilled = this.s.filled; 31572 this.s.filled = false; 31573 // Remove any previous value elements 31574 // Timeout is annoying but because of IOS 31575 setTimeout(function () { 31576 _this.dom.defaultValue.remove(); 31577 }, 50); 31578 var _loop_3 = function (val) { 31579 // Timeout is annoying but because of IOS 31580 setTimeout(function () { 31581 if (val !== undefined) { 31582 val.remove(); 31583 } 31584 }, 50); 31585 }; 31586 for (var _i = 0, _a = this.dom.value; _i < _a.length; _i++) { 31587 var val = _a[_i]; 31588 _loop_3(val); 31589 } 31590 var children = this.dom.inputCont.children();
31591 if (children.length > 1) { 31592 // eslint-disable-next-line @typescript-eslint/prefer-for-of 31593 for (var i = 0; i < children.length; i++) { 31594 $$3(children[i]).remove(); 31595 } 31596 } 31597 // Find the column with the title matching the data for the criteria and take note of the index 31598 if (loadedCriteria !== undefined) { 31599 this.s.dt.columns().every(function (index) { 31600 if (_this.s.dt.settings()[0].aoColumns[index].sTitle === loadedCriteria.data) { 31601 _this.s.dataIdx = index; 31602 } 31603 }); 31604 } 31605 // Initialise the value elements based on the condition 31606 this.dom.value = [].concat(this.s.conditions[this.s.condition].init(this, Criteria.updateListener, loadedCriteria !== undefined ? loadedCriteria.value : undefined)); 31607 if (loadedCriteria !== undefined && loadedCriteria.value !== undefined) { 31608 this.s.value = loadedCriteria.value; 31609 } 31610 this.dom.inputCont.empty(); 31611 // Insert value elements and trigger the inserted event 31612 if (this.dom.value[0] !== undefined) { 31613 this.dom.value[0] 31614 .appendTo(this.dom.inputCont) 31615 .trigger('dtsb-inserted'); 31616 } 31617 for (var i = 1; i < this.dom.value.length; i++) { 31618 this.dom.value[i] 31619 .insertAfter(this.dom.value[i - 1]) 31620 .trigger('dtsb-inserted'); 31621 } 31622 // Check if the criteria can be used in a search 31623 this.s.filled = this.s.conditions[this.s.condition].isInputValid(this.dom.value, this); 31624 this.setListeners(); 31625 // If it can and this is different to before then trigger a draw 31626 if (!this.s.preventRedraw && prevFilled !== this.s.filled) { 31627 // If using SSP we want to restrict the amount of server calls that take place 31628 // and this will already have taken place 31629 if (!this.s.dt.page.info().serverSide) { 31630 this.s.dt.draw(); 31631 } 31632 this.setListeners(); 31633 } 31634 }; 31635 /** 31636 * Provides throttling capabilities to SearchBuilder without having to use dt's _fnThrottle function 31637 * This is because that function is not quite suitable for our needs as it runs initially rather than waiting 31638 * 31639 * @param args arguments supplied to the throttle function 31640 * @returns Function that is to be run that implements the throttling 31641 */ 31642 Criteria.prototype._throttle = function (fn, frequency) { 31643 if (frequency === void 0) { frequency = 200; } 31644 var last = null; 31645 var timer = null; 31646 var that = this; 31647 if (frequency === null) { 31648 frequency = 200; 31649 } 31650 return function () { 31651 var args = []; 31652 for (var _i = 0; _i < arguments.length; _i++) { 31653 args[_i] = arguments[_i]; 31654 } 31655 var now = +new Date(); 31656 if (last !== null && now < last + frequency) { 31657 clearTimeout(timer); 31658 } 31659 else { 31660 last = now; 31661 } 31662 timer = setTimeout(function () { 31663 last = null; 31664 fn.apply(that, args); 31665 }, frequency); 31666 }; 31667 }; 31668 Criteria.version = '1.1.0'; 31669 Criteria.classes = { 31670 button: 'dtsb-button', 31671 buttonContainer: 'dtsb-buttonContainer', 31672 condition: 'dtsb-condition', 31673 container: 'dtsb-criteria', 31674 data: 'dtsb-data', 31675 "delete": 'dtsb-delete', 31676 dropDown: 'dtsb-dropDown', 31677 greyscale: 'dtsb-greyscale', 31678 input: 'dtsb-input', 31679 inputCont: 'dtsb-inputCont',
31680 italic: 'dtsb-italic', 31681 joiner: 'dtsp-joiner', 31682 left: 'dtsb-left', 31683 notItalic: 'dtsb-notItalic', 31684 option: 'dtsb-option', 31685 right: 'dtsb-right', 31686 select: 'dtsb-select', 31687 value: 'dtsb-value', 31688 vertical: 'dtsb-vertical' 31689 }; 31690 /** 31691 * Default initialisation function for select conditions 31692 */ 31693 Criteria.initSelect = function (that, fn, preDefined, array) { 31694 if (preDefined === void 0) { preDefined = null; } 31695 if (array === void 0) { array = false; } 31696 var column = that.dom.data.children('option:selected').val(); 31697 var indexArray = that.s.dt.rows().indexes().toArray(); 31698 var settings = that.s.dt.settings()[0]; 31699 that.dom.valueTitle.prop('selected', true); 31700 // Declare select element to be used with all of the default classes and listeners. 31701 var el = $$3('<select/>') 31702 .addClass(Criteria.classes.value) 31703 .addClass(Criteria.classes.dropDown) 31704 .addClass(Criteria.classes.italic) 31705 .addClass(Criteria.classes.select) 31706 .append(that.dom.valueTitle) 31707 .on('change.dtsb', function () { 31708 $$3(this).removeClass(Criteria.classes.italic); 31709 fn(that, this); 31710 }); 31711 if (that.c.greyscale) { 31712 el.addClass(Criteria.classes.greyscale); 31713 } 31714 var added = []; 31715 var options = []; 31716 // Add all of the options from the table to the select element. 31717 // Only add one option for each possible value 31718 for (var _i = 0, indexArray_1 = indexArray; _i < indexArray_1.length; _i++) { 31719 var index = indexArray_1[_i]; 31720 var filter = settings.oApi._fnGetCellData(settings, index, column, typeof that.c.orthogonal === 'string' ? 31721 that.c.orthogonal : 31722 that.c.orthogonal.search); 31723 var value = { 31724 filter: typeof filter === 'string' ? 31725 filter.replace(/[\r\n\u2028]/g, ' ') : // Need to replace certain characters to match search values 31726 filter, 31727 index: index, 31728 text: settings.oApi._fnGetCellData(settings, index, column, typeof that.c.orthogonal === 'string' ? 31729 that.c.orthogonal : 31730 that.c.orthogonal.display) 31731 }; 31732 // If we are dealing with an array type, either make sure we are working with arrays, or sort them 31733 if (that.s.type === 'array') { 31734 value.filter = !Array.isArray(value.filter) ? [value.filter] : value.filter; 31735 value.text = !Array.isArray(value.text) ? [value.text] : value.text; 31736 } 31737 // Function to add an option to the select element 31738 var addOption = function (filt, text) { 31739 if (that.s.type.includes('html') && filt !== null && typeof filt === 'string') { 31740 filt.replace(/(<([^>]+)>)/ig, ''); 31741 } 31742 // Add text and value, stripping out any html if that is the column type 31743 var opt = $$3('<option>', { 31744 type: Array.isArray(filt) ? 'Array' : 'String', 31745 value: filt 31746 }) 31747 .data('sbv', filt) 31748 .addClass(that.classes.option) 31749 .addClass(that.classes.notItalic) 31750 // Have to add the text this way so that special html characters are not escaped - & etc. 31751 .html(typeof text === 'string' ? 31752 text.replace(/(<([^>]+)>)/ig, '') : 31753 text); 31754 var val = opt.val(); 31755 // Check that this value has not already been added 31756 if (added.indexOf(val) === -1) { 31757 added.push(val); 31758 options.push(opt); 31759 if (preDefined !== null && Array.isArray(preDefined[0])) { 31760 preDefined[0] = preDefined[0].sort().join(','); 31761 } 31762 // If this value was previously selected as indicated by preDefined, then select it again 31763 if (preDefined !== null && opt.val() === preDefined[0]) { 31764 opt.prop('selected', true); 31765 el.removeClass(Criteria.classes.italic); 31766 that.dom.valueTitle.removeProp('selected'); 31767 } 31768 } 31769 }; 31770 // If this is to add the individual values within the array we need to loop over the array 31771 if (array) { 31772 for (var i = 0; i < value.filter.length; i++) { 31773 addOption(value.filter[i], value.text[i]); 31774 } 31775 } 31776 // Otherwise the value that is in the cell is to be added 31777 else { 31778 addOption(value.filter, Array.isArray(value.text) ? value.text.join(', ') : value.text); 31779 } 31780 } 31781 options.sort(function (a, b) { 31782 if (that.s.type === 'array' || 31783 that.s.type === 'string' || 31784 that.s.type === 'html') { 31785 if (a.val() < b.val()) { 31786 return -1; 31787 } 31788 else if (a.val() > b.val()) { 31789 return 1; 31790 } 31791 else { 31792 return 0; 31793 } 31794 } 31795 else if (that.s.type === 'num' || 31796 that.s.type === 'html-num') { 31797 if (+a.val().replace(/(<([^>]+)>)/ig, '') < +b.val().replace(/(<([^>]+)>)/ig, '')) { 31798 return -1; 31799 } 31800 else if (+a.val().replace(/(<([^>]+)>)/ig, '') > +b.val().replace(/(<([^>]+)>)/ig, '')) { 31801 return 1; 31802 } 31803 else { 31804 return 0; 31805 } 31806 } 31807 else if (that.s.type === 'num-fmt' || that.s.type === 'html-num-fmt') { 31808 if (+a.val().replace(/[^0-9.]/g, '') < +b.val().replace(/[^0-9.]/g, '')) { 31809 return -1; 31810 } 31811 else if (+a.val().replace(/[^0-9.]/g, '') > +b.val().replace(/[^0-9.]/g, '')) { 31812 return 1; 31813 } 31814 else { 31815 return 0; 31816 } 31817 } 31818 }); 31819 for (var _a = 0, options_1 = options; _a < options_1.length; _a++) { 31820 var opt = options_1[_a]; 31821 el.append(opt); 31822 } 31823 return el; 31824 }; 31825 /** 31826 * Default initialisation function for select conditions 31827 */ 31828 Criteria.initSelectSSP = function (that, fn, preDefined) { 31829 if (preDefined === void 0) { preDefined = null; } 31830 that.dom.valueTitle.prop('selected', true); 31831 // Declare select element to be used with all of the default classes and listeners. 31832 var el = $$3('<select/>') 31833 .addClass(Criteria.classes.value) 31834 .addClass(Criteria.classes.dropDown) 31835 .addClass(Criteria.classes.italic) 31836 .addClass(Criteria.classes.select) 31837 .append(that.dom.valueTitle) 31838 .on('change.dtsb', function () { 31839 $$3(this).removeClass(Criteria.classes.italic); 31840 fn(that, this); 31841 }); 31842 if (that.c.greyscale) { 31843 el.addClass(Criteria.classes.greyscale); 31844 } 31845 var options = []; 31846 for (var _i = 0, _a = that.s.serverData[that.s.origData]; _i < _a.length; _i++) { 31847 var option = _a[_i]; 31848 var value = option.value; 31849 var label = option.label; 31850 // Function to add an option to the select element 31851 var addOption = function (filt, text) { 31852 if (that.s.type.includes('html') && filt !== null && typeof filt === 'string') { 31853 filt.replace(/(<([^>]+)>)/ig, ''); 31854 } 31855 // Add text and value, stripping out any html if that is the column type 31856 var opt = $$3('<option>', { 31857 type: Array.isArray(filt) ? 'Array' : 'String', 31858 value: filt 31859 }) 31860 .data('sbv', filt) 31861 .addClass(that.classes.option) 31862 .addClass(that.classes.notItalic) 31863 // Have to add the text this way so that special html characters are not escaped - & etc. 31864 .html(typeof text === 'string' ? 31865 text.replace(/(<([^>]+)>)/ig, '') : 31866 text); 31867 options.push(opt); 31868 // If this value was previously selected as indicated by preDefined, then select it again 31869 if (preDefined !== null && opt.val() === preDefined[0]) { 31870 opt.prop('selected', true); 31871 el.removeClass(Criteria.classes.italic); 31872 that.dom.valueTitle.removeProp('selected'); 31873 } 31874 }; 31875 addOption(value, label); 31876 } 31877 for (var _b = 0, options_2 = options; _b < options_2.length; _b++) { 31878 var opt = options_2[_b]; 31879 el.append(opt); 31880 } 31881 return el; 31882 }; 31883 /** 31884 * Default initialisation function for select array conditions 31885 * 31886 * This exists because there needs to be different select functionality for contains/without and equals/not 31887 */ 31888 Criteria.initSelectArray = function (that, fn, preDefined) { 31889 if (preDefined === void 0) { preDefined = null; } 31890 return Criteria.initSelect(that, fn, preDefined, true); 31891 }; 31892 /** 31893 * Default initialisation function for input conditions 31894 */ 31895 Criteria.initInput = function (that, fn, preDefined) { 31896 if (preDefined === void 0) { preDefined = null; } 31897 // Declare the input element 31898 var searchDelay = that.s.dt.settings()[0].searchDelay; 31899 var el = $$3('<input/>') 31900 .addClass(Criteria.classes.value) 31901 .addClass(Criteria.classes.input) 31902 .on('input.dtsb keypress.dtsb', that._throttle(function (e) { 31903 var code = e.keyCode || e.which; 31904 return fn(that, this, code); 31905 }, searchDelay === null ? 100 : searchDelay)); 31906 if (that.c.greyscale) { 31907 el.addClass(Criteria.classes.greyscale); 31908 } 31909 // If there is a preDefined value then add it 31910 if (preDefined !== null) { 31911 el.val(preDefined[0]); 31912 } 31913 // This is add responsive functionality to the logic button without redrawing everything else 31914 that.s.dt.one('draw.dtsb', function () { 31915 that.s.topGroup.trigger('dtsb-redrawLogic'); 31916 }); 31917 return el; 31918 }; 31919 /** 31920 * Default initialisation function for conditions requiring 2 inputs 31921 */ 31922 Criteria.init2Input = function (that, fn, preDefined) { 31923 if (preDefined === void 0) { preDefined = null; } 31924 // Declare all of the necessary jQuery elements 31925 var searchDelay = that.s.dt.settings()[0].searchDelay; 31926 var els = [ 31927 $$3('<input/>') 31928 .addClass(Criteria.classes.value) 31929 .addClass(Criteria.classes.input) 31930 .on('input.dtsb keypress.dtsb', that._throttle(function (e) { 31931 var code = e.keyCode || e.which; 31932 return fn(that, this, code); 31933 }, searchDelay === null ? 100 : searchDelay)), 31934 $$3('<span>') 31935 .addClass(that.classes.joiner) 31936 .html(that.s.dt.i18n('searchBuilder.valueJoiner', that.c.i18n.valueJoiner)), 31937 $$3('<input/>') 31938 .addClass(Criteria.classes.value) 31939 .addClass(Criteria.classes.input) 31940 .on('input.dtsb keypress.dtsb', that._throttle(function (e) { 31941 var code = e.keyCode || e.which; 31942 return fn(that, this, code); 31943 }, searchDelay === null ? 100 : searchDelay)) 31944 ]; 31945 if (that.c.greyscale) { 31946 els[0].addClass(Criteria.classes.greyscale); 31947 els[2].addClass(Criteria.classes.greyscale); 31948 } 31949 // If there is a preDefined value then add it 31950 if (preDefined !== null) { 31951 els[0].val(preDefined[0]); 31952 els[2].val(preDefined[1]); 31953 } 31954 // This is add responsive functionality to the logic button without redrawing everything else 31955 that.s.dt.one('draw.dtsb', function () { 31956 that.s.topGroup.trigger('dtsb-redrawLogic'); 31957 }); 31958 return els; 31959 }; 31960 /** 31961 * Default initialisation function for date conditions 31962 */ 31963 Criteria.initDate = function (that, fn, preDefined) { 31964 if (preDefined === void 0) { preDefined = null; } 31965 var searchDelay = that.s.dt.settings()[0].searchDelay; 31966 // Declare date element using DataTables dateTime plugin 31967 var el = $$3('<input/>') 31968 .addClass(Criteria.classes.value) 31969 .addClass(Criteria.classes.input) 31970 .dtDateTime({ 31971 attachTo: 'input', 31972 format: that.s.dateFormat ? that.s.dateFormat : undefined 31973 }) 31974 .on('change.dtsb', that._throttle(function () { 31975 return fn(that, this); 31976 }, searchDelay === null ? 100 : searchDelay)) 31977 .on('input.dtsb keypress.dtsb', function (e) { 31978 that._throttle(function () { 31979 var code = e.keyCode || e.which; 31980 return fn(that, this, code); 31981 }, searchDelay === null ? 100 : searchDelay); 31982 }); 31983 if (that.c.greyscale) { 31984 el.addClass(Criteria.classes.greyscale); 31985 } 31986 // If there is a preDefined value then add it 31987 if (preDefined !== null) { 31988 el.val(preDefined[0]); 31989 } 31990 // This is add responsive functionality to the logic button without redrawing everything else 31991 that.s.dt.one('draw.dtsb', function () { 31992 that.s.topGroup.trigger('dtsb-redrawLogic'); 31993 }); 31994 return el; 31995 }; 31996 Criteria.initNoValue = function (that) { 31997 // This is add responsive functionality to the logic button without redrawing everything else 31998 that.s.dt.one('draw.dtsb', function () { 31999 that.s.topGroup.trigger('dtsb-redrawLogic'); 32000 }); 32001 return []; 32002 }; 32003 Criteria.init2Date = function (that, fn, preDefined) { 32004 var _this = this; 32005 if (preDefined === void 0) { preDefined = null; } 32006 var searchDelay = that.s.dt.settings()[0].searchDelay; 32007 // Declare all of the date elements that are required using DataTables dateTime plugin 32008 var els = [ 32009 $$3('<input/>') 32010 .addClass(Criteria.classes.value) 32011 .addClass(Criteria.classes.input) 32012 .dt
32012DateTime({ 32013 attachTo: 'input', 32014 format: that.s.dateFormat ? that.s.dateFormat : undefined 32015 }) 32016 .on('change.dtsb', searchDelay !== null ? 32017 that.s.dt.settings()[0].oApi._fnThrottle(function () { 32018 return fn(that, this); 32019 }, searchDelay) : 32020 function () { 32021 fn(that, _this); 32022 }) 32023 .on('input.dtsb keypress.dtsb', function (e) { 32024 that.s.dt.settings()[0].oApi._fnThrottle(function () { 32025 var code = e.keyCode || e.which; 32026 return fn(that, this, code); 32027 }, searchDelay === null ? 0 : searchDelay); 32028 }), 32029 $$3('<span>') 32030 .addClass(that.classes.joiner) 32031 .html(that.s.dt.i18n('searchBuilder.valueJoiner', that.c.i18n.valueJoiner)), 32032 $$3('<input/>') 32033 .addClass(Criteria.classes.value) 32034 .addClass(Criteria.classes.input) 32035 .dtDateTime({ 32036 attachTo: 'input', 32037 format: that.s.dateFormat ? that.s.dateFormat : undefined 32038 }) 32039 .on('change.dtsb', searchDelay !== null ? 32040 that.s.dt.settings()[0].oApi._fnThrottle(function () { 32041 return fn(that, this); 32042 }, searchDelay) : 32043 function () { 32044 fn(that, _this); 32045 }) 32046 .on('input.dtsb keypress.dtsb', !that.c.enterSearch && 32047 !(that.s.dt.settings()[0].oInit.search !== undefined && 32048 that.s.dt.settings()[0].oInit.search["return"]) && 32049 searchDelay !== null ? 32050 that.s.dt.settings()[0].oApi._fnThrottle(function () { 32051 return fn(that, this); 32052 }, searchDelay) : 32053 function (e) { 32054 var code = e.keyCode || e.which; 32055 fn(that, _this, code); 32056 }) 32057 ]; 32058 if (that.c.greyscale) { 32059 els[0].addClass(Criteria.classes.greyscale); 32060 els[2].addClass(Criteria.classes.greyscale); 32061 } 32062 // If there are and preDefined values then add them 32063 if (preDefined !== null && preDefined.length > 0) { 32064 els[0].val(preDefined[0]); 32065 els[2].val(preDefined[1]); 32066 } 32067 // This is add responsive functionality to the logic button without redrawing everything else 32068 that.s.dt.one('draw.dtsb', function () { 32069 that.s.topGroup.trigger('dtsb-redrawLogic'); 32070 }); 32071 return els; 32072 }; 32073 /** 32074 * Default function for select elements to validate condition 32075 */ 32076 Criteria.isInputValidSelect = function (el) { 32077 var allFilled = true; 32078 // Check each element to make sure that the selections are valid 32079 for (var _i = 0, el_1 = el; _i < el_1.length; _i++) { 32080 var element = el_1[_i]; 32081 if (element.children('option:selected').length === 32082 element.children('option').length - 32083 element.children('option.' + Criteria.classes.notItalic).length && 32084 element.children('option:selected').length === 1 && 32085 element.children('option:selected')[0] === element.children('option')[0]) { 32086 allFilled = false; 32087 } 32088 } 32089 return allFilled; 32090 }; 32091 /** 32092 * Default function for input and date elements to validate condition 32093 */ 32094 Criteria.isInputValidInput = function (el) { 32095 var allFilled = true; 32096 // Check each element to make sure that the inputs are valid 32097 for (var _i = 0, el_2 = el; _i < el_2.length; _i++) { 32098 var element = el_2[_i]; 32099 if (element.is('input') && element.val().length === 0) { 32100 allFilled = false; 32101 } 32102 } 32103 return allFilled; 32104 }; 32105 /** 32106 * Default function for getting select conditions 32107 */ 32108 Criteria.inputValueSelect = function (el) { 32109 var values = []; 32110 // Go through the select elements and push each selected option to the return array 32111 for (var _i = 0, el_3 = el; _i < el_3.length; _i++) { 32112 var element = el_3[_i];
32113 if (element.is('select')) { 32114 values.push(Criteria._escapeHTML(element.children('option:selected').data('sbv'))); 32115 } 32116 } 32117 return values; 32118 }; 32119 /** 32120 * Default function for getting input conditions 32121 */ 32122 Criteria.inputValueInput = function (el) { 32123 var values = []; 32124 // Go through the input elements and push each value to the return array 32125 for (var _i = 0, el_4 = el; _i < el_4.length; _i++) { 32126 var element = el_4[_i]; 32127 if (element.is('input')) { 32128 values.push(Criteria._escapeHTML(element.val())); 32129 } 32130 } 32131 return values; 32132 }; 32133 /** 32134 * Function that is run on each element as a call back when a search should be triggered 32135 */ 32136 Criteria.updateListener = function (that, el, code) { 32137 // When the value is changed the criteria is now complete so can be included in searches 32138 // Get the condition from the map based on the key that has been selected for the condition 32139 var condition = that.s.conditions[that.s.condition]; 32140 that.s.filled = condition.isInputValid(that.dom.value, that); 32141 that.s.value = condition.inputValue(that.dom.value, that); 32142 if (!that.s.filled) { 32143 if (!that.c.enterSearch && 32144 !(that.s.dt.settings()[0].oInit.search !== undefined && 32145 that.s.dt.settings()[0].oInit.search["return"]) || 32146 code === 13) { 32147 that.s.dt.draw(); 32148 } 32149 return; 32150 } 32151 if (!Array.isArray(that.s.value)) { 32152 that.s.value = [that.s.value]; 32153 } 32154 for (var i = 0; i < that.s.value.length; i++) { 32155 // If the value is an array we need to sort it 32156 if (Array.isArray(that.s.value[i])) { 32157 that.s.value[i].sort(); 32158 } 32159 // Otherwise replace the decimal place character for i18n 32160 else if (that.s.type.includes('num') && 32161 (that.s.dt.settings()[0].oLanguage.sDecimal !== '' || 32162 that.s.dt.settings()[0].oLanguage.sThousands !== '')) {
32163 var splitRD = [that.s.value[i].toString()]; 32164 if (that.s.dt.settings()[0].oLanguage.sDecimal !== '') { 32165 splitRD = that.s.value[i].split(that.s.dt.settings()[0].oLanguage.sDecimal); 32166 } 32167 if (that.s.dt.settings()[0].oLanguage.sThousands !== '') { 32168 for (var j = 0; j < splitRD.length; j++) { 32169 splitRD[j] = splitRD[j].replace(that.s.dt.settings()[0].oLanguage.sThousands, ','); 32170 } 32171 } 32172 that.s.value[i] = splitRD.join('.'); 32173 } 32174 } 32175 // Take note of the cursor position so that we can refocus there later 32176 var idx = null; 32177 var cursorPos = null; 32178 for (var i = 0; i < that.dom.value.length; i++) { 32179 if (el === that.dom.value[i][0]) { 32180 idx = i; 32181 if (el.selectionStart !== undefined) { 32182 cursorPos = el.selectionStart; 32183 } 32184 } 32185 } 32186 if (!that.c.enterSearch && 32187 !(that.s.dt.settings()[0].oInit.search !== undefined && 32188 that.s.dt.settings()[0].oInit.search["return"]) || 32189 code === 13) { 32190 // Trigger a search 32191 that.s.dt.draw(); 32192 } 32193 // Refocus the element and set the correct cursor position 32194 if (idx !== null) { 32195 that.dom.value[idx].removeClass(that.classes.italic); 32196 that.dom.value[idx].focus(); 32197 if (cursorPos !== null) { 32198 that.dom.value[idx][0].setSelectionRange(cursorPos, cursorPos); 32199 } 32200 } 32201 }; 32202 // The order of the conditions will make eslint sad :( 32203 // Has to be in this order so that they are displayed correctly in select elements 32204 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32205 // eslint-disable-next-line @typescript-eslint/member-ordering 32206 Criteria.dateConditions = { 32207 '=': { 32208 conditionName: function (dt, i18n) { 32209 return dt.i18n('searchBuilder.conditions.date.equals', i18n.conditions.date.equals); 32210 }, 32211 init: Criteria.initDate, 32212 inputValue: Criteria.inputValueInput, 32213 isInputValid: Criteria.isInputValidInput, 32214 search: function (value, comparison) { 32215 value = value.replace(/(\/|-|,)/g, '-'); 32216 return value === comparison[0]; 32217 } 32218 }, 32219 // eslint-disable-next-line sort-keys 32220 '!=': { 32221 conditionName: function (dt, i18n) { 32222 return dt.i18n('searchBuilder.conditions.date.not', i18n.conditions.date.not); 32223 }, 32224 init: Criteria.initDate, 32225 inputValue: Criteria.inputValueInput, 32226 isInputValid: Criteria.isInputValidInput, 32227 search: function (value, comparison) { 32228 value = value.replace(/(\/|-|,)/g, '-'); 32229 return value !== comparison[0]; 32230 } 32231 }, 32232 '<': { 32233 conditionName: function (dt, i18n) { 32234 return dt.i18n('searchBuilder.conditions.date.before', i18n.conditions.date.before); 32235 }, 32236 init: Criteria.initDate, 32237 inputValue: Criteria.inputValueInput, 32238 isInputValid: Criteria.isInputValidInput, 32239 search: function (value, comparison) { 32240 value = value.replace(/(\/|-|,)/g, '-'); 32241 return value < comparison[0]; 32242 } 32243 }, 32244 '>': { 32245 conditionName: function (dt, i18n) { 32246 return dt.i18n('searchBuilder.conditions.date.after', i18n.conditions.date.after); 32247 }, 32248 init: Criteria.initDate, 32249 inputValue: Criteria.inputValueInput, 32250 isInputValid: Criteria.isInputValidInput, 32251 search: function (value, comparison) { 32252 value = value.replace(/(\/|-|,)/g, '-'); 32253 return value > comparison[0]; 32254 } 32255 }, 32256 'between': { 32257 conditionName: function (dt, i18n) { 32258 return dt.i18n('searchBuilder.conditions.date.between', i18n.conditions.date.between); 32259 }, 32260 init: Criteria.init2Date, 32261 inputValue: Criteria.inputValueInput, 32262 isInputValid: Criteria.isInputValidInput, 32263 search: function (value, comparison) { 32264 value = value.replace(/(\/|-|,)/g, '-'); 32265 if (comparison[0] < comparison[1]) { 32266 return comparison[0] <= value && value <= comparison[1]; 32267 } 32268 else { 32269 return comparison[1] <= value && value <= comparison[0]; 32270 } 32271 } 32272 }, 32273 // eslint-disable-next-line sort-keys 32274 '!between': { 32275 conditionName: function (dt, i18n) { 32276 return dt.i18n('searchBuilder.conditions.date.notBetween', i18n.conditions.date.notBetween); 32277 }, 32278 init: Criteria.init2Date, 32279 inputValue: Criteria.inputValueInput, 32280 isInputValid: Criteria.isInputValidInput, 32281 search: function (value, comparison) { 32282 value = value.replace(/(\/|-|,)/g, '-'); 32283 if (comparison[0] < comparison[1]) { 32284 return !(comparison[0] <= value && value <= comparison[1]); 32285 } 32286 else { 32287 return !(comparison[1] <= value && value <= comparison[0]); 32288 } 32289 } 32290 }, 32291 'null': { 32292 conditionName: function (dt, i18n) { 32293 return dt.i18n('searchBuilder.conditions.date.empty', i18n.conditions.date.empty); 32294 }, 32295 init: Criteria.initNoValue, 32296 inputValue: function () { 32297 return; 32298 }, 32299 isInputValid: function () { 32300 return true; 32301 }, 32302 search: function (value) { 32303 return value === null || value === undefined || value.length === 0; 32304 } 32305 }, 32306 // eslint-disable-next-line sort-keys 32307 '!null': { 32308 conditionName: function (dt, i18n) { 32309 return dt.i18n('searchBuilder.conditions.date.notEmpty', i18n.conditions.date.notEmpty); 32310 }, 32311 init: Criteria.initNoValue, 32312 inputValue: function () { 32313 return; 32314 }, 32315 isInputValid: function () { 32316 return true; 32317 }, 32318 search: function (value) { 32319 return !(value === null || value === undefined || value.length === 0); 32320 } 32321 } 32322 }; 32323 // The order of the conditions will make eslint sad :( 32324 // Has to be in this order so that they are displayed correctly in select elements 32325 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32326 // eslint-disable-next-line @typescript-eslint/member-ordering 32327 Criteria.momentDateConditions = { 32328 '=': { 32329 conditionName: function (dt, i18n) { 32330 return dt.i18n('searchBuilder.conditions.date.equals', i18n.conditions.date.equals); 32331 }, 32332 init: Criteria.initDate, 32333 inputValue: Criteria.inputValueInput, 32334 isInputValid: Criteria.isInputValidInput, 32335 search: function (value, comparison, that) { 32336 return moment()(value, that.s.dateFormat).valueOf() === 32337 moment()(comparison[0], that.s.dateFormat).valueOf(); 32338 } 32339 }, 32340 // eslint-disable-next-line sort-keys 32341 '!=': { 32342 conditionName: function (dt, i18n) { 32343 return dt.i18n('searchBuilder.conditions.date.not', i18n.conditions.date.not); 32344 }, 32345 init: Criteria.initDate, 32346 inputValue: Criteria.inputValueInput, 32347 isInputValid: Criteria.isInputValidInput, 32348 search: function (value, comparison, that) { 32349 return moment()(value, that.s.dateFormat).valueOf() !== 32350 moment()(comparison[0], that.s.dateFormat).valueOf(); 32351 } 32352 }, 32353 '<': { 32354 conditionName: function (dt, i18n) { 32355 return dt.i18n('searchBuilder.conditions.date.before', i18n.conditions.date.before); 32356 }, 32357 init: Criteria.initDate, 32358 inputValue: Criteria.inputValueInput, 32359 isInputValid: Criteria.isInputValidInput, 32360 search: function (value, comparison, that) { 32361 return moment()(value, that.s.dateFormat).valueOf() < moment()(comparison[0], that.s.dateFormat).valueOf(); 32362 } 32363 }, 32364 '>': { 32365 conditionName: function (dt, i18n) { 32366 return dt.i18n('searchBuilder.conditions.date.after', i18n.conditions.date.after); 32367 }, 32368 init: Criteria.initDate, 32369 inputValue: Criteria.inputValueInput, 32370 isInputValid: Criteria.isInputValidInput, 32371 search: function (value, comparison, that) { 32372 return moment()(value, that.s.dateFormat).valueOf() > moment()(comparison[0], that.s.dateFormat).valueOf(); 32373 } 32374 }, 32375 'between': { 32376 conditionName: function (dt, i18n) { 32377 return dt.i18n('searchBuilder.conditions.date.between', i18n.conditions.date.between); 32378 }, 32379 init: Criteria.init2Date, 32380 inputValue: Criteria.inputValueInput, 32381 isInputValid: Criteria.isInputValidInput, 32382 search: function (value, comparison, that) { 32383 var val = moment()(value, that.s.dateFormat).valueOf(); 32384 var comp0 = moment()(comparison[0], that.s.dateFormat).valueOf(); 32385 var comp1 = moment()(comparison[1], that.s.dateFormat).valueOf(); 32386 if (comp0 < comp1) { 32387 return comp0 <= val && val <= comp1; 32388 } 32389 else { 32390 return comp1 <= val && val <= comp0; 32391 } 32392 } 32393 }, 32394 // eslint-disable-next-line sort-keys 32395 '!between': { 32396 conditionName: function (dt, i18n) { 32397 return dt.i18n('searchBuilder.conditions.date.notBetween', i18n.conditions.date.notBetween); 32398 }, 32399 init: Criteria.init2Date, 32400 inputValue: Criteria.inputValueInput, 32401 isInputValid: Criteria.isInputValidInput, 32402 search: function (value, comparison, that) { 32403 var val = moment()(value, that.s.dateFormat).valueOf(); 32404 var comp0 = moment()(comparison[0], that.s.dateFormat).valueOf(); 32405 var comp1 = moment()(comparison[1], that.s.dateFormat).valueOf(); 32406 if (comp0 < comp1) { 32407 return !(+comp0 <= +val && +val <= +comp1); 32408 } 32409 else { 32410 return !(+comp1 <= +val && +val <= +comp0); 32411 } 32412 } 32413 }, 32414 'null': { 32415 conditionName: function (dt, i18n) { 32416 return dt.i18n('searchBuilder.conditions.date.empty', i18n.conditions.date.empty); 32417 }, 32418 init: Criteria.initNoValue, 32419 inputValue: function () { 32420 return; 32421 }, 32422 isInputValid: function () { 32423 return true; 32424 }, 32425 search: function (value) { 32426 return value === null || value === undefined || value.length === 0; 32427 } 32428 }, 32429 // eslint-disable-next-line sort-keys 32430 '!null': { 32431 conditionName: function (dt, i18n) { 32432 return dt.i18n('searchBuilder.conditions.date.notEmpty', i18n.conditions.date.notEmpty); 32433 }, 32434 init: Criteria.initNoValue, 32435 inputValue: function () { 32436 return; 32437 }, 32438 isInputValid: function () { 32439 return true; 32440 }, 32441 search: function (value) { 32442 return !(value === null || value === undefined || value.length === 0); 32443 } 32444 } 32445 }; 32446 // The order of the conditions will make eslint sad :( 32447 // Has to be in this order so that they are displayed correctly in select elements 32448 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32449 // eslint-disable-next-line @typescript-eslint/member-ordering 32450 Criteria.luxonDateConditions = { 32451 '=': { 32452 conditionName: function (dt, i18n) { 32453 return dt.i18n('searchBuilder.conditions.date.equals', i18n.conditions.date.equals); 32454 }, 32455 init: Criteria.initDate, 32456 inputValue: Criteria.inputValueInput, 32457 isInputValid: Criteria.isInputValidInput, 32458 search: function (value, comparison, that) { 32459 return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts 32460 === luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32461 } 32462 }, 32463 // eslint-disable-next-line sort-keys 32464 '!=': { 32465 conditionName: function (dt, i18n) { 32466 return dt.i18n('searchBuilder.conditions.date.not', i18n.conditions.date.not); 32467 }, 32468 init: Criteria.initDate, 32469 inputValue: Criteria.inputValueInput, 32470 isInputValid: Criteria.isInputValidInput, 32471 search: function (value, comparison, that) { 32472 return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts 32473 !== luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32474 } 32475 }, 32476 '<': { 32477 conditionName: function (dt, i18n) { 32478 return dt.i18n('searchBuilder.conditions.date.before', i18n.conditions.date.before); 32479 }, 32480 init: Criteria.initDate, 32481 inputValue: Criteria.inputValueInput, 32482 isInputValid: Criteria.isInputValidInput, 32483 search: function (value, comparison, that) { 32484 return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts 32485 < luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32486 } 32487 }, 32488 '>': { 32489 conditionName: function (dt, i18n) { 32490 return dt.i18n('searchBuilder.conditions.date.after', i18n.conditions.date.after); 32491 }, 32492 init: Criteria.initDate, 32493 inputValue: Criteria.inputValueInput, 32494 isInputValid: Criteria.isInputValidInput, 32495 search: function (value, comparison, that) { 32496 return luxon().DateTime.fromFormat(value, that.s.dateFormat).ts
32496 32497 > luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32498 } 32499 }, 32500 'between': { 32501 conditionName: function (dt, i18n) { 32502 return dt.i18n('searchBuilder.conditions.date.between', i18n.conditions.date.between); 32503 }, 32504 init: Criteria.init2Date, 32505 inputValue: Criteria.inputValueInput, 32506 isInputValid: Criteria.isInputValidInput, 32507 search: function (value, comparison, that) { 32508 var val = luxon().DateTime.fromFormat(value, that.s.dateFormat).ts; 32509 var comp0 = luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32510 var comp1 = luxon().DateTime.fromFormat(comparison[1], that.s.dateFormat).ts; 32511 if (comp0 < comp1) { 32512 return comp0 <= val && val <= comp1; 32513 } 32514 else { 32515 return comp1 <= val && val <= comp0; 32516 } 32517 } 32518 }, 32519 // eslint-disable-next-line sort-keys 32520 '!between': { 32521 conditionName: function (dt, i18n) { 32522 return dt.i18n('searchBuilder.conditions.date.notBetween', i18n.conditions.date.notBetween); 32523 }, 32524 init: Criteria.init2Date, 32525 inputValue: Criteria.inputValueInput, 32526 isInputValid: Criteria.isInputValidInput, 32527 search: function (value, comparison, that) { 32528 var val = luxon().DateTime.fromFormat(value, that.s.dateFormat).ts; 32529 var comp0 = luxon().DateTime.fromFormat(comparison[0], that.s.dateFormat).ts; 32530 var comp1 = luxon().DateTime.fromFormat(comparison[1], that.s.dateFormat).ts; 32531 if (comp0 < comp1) { 32532 return !(+comp0 <= +val && +val <= +comp1); 32533 } 32534 else { 32535 return !(+comp1 <= +val && +val <= +comp0); 32536 } 32537 } 32538 }, 32539 'null': { 32540 conditionName: function (dt, i18n) { 32541 return dt.i18n('searchBuilder.conditions.date.empty', i18n.conditions.date.empty); 32542 }, 32543 init: Criteria.initNoValue, 32544 inputValue: function () { 32545 return; 32546 }, 32547 isInputValid: function () { 32548 return true; 32549 }, 32550 search: function (value) { 32551 return value === null || value === undefined || value.length === 0; 32552 } 32553 }, 32554 // eslint-disable-next-line sort-keys 32555 '!null': { 32556 conditionName: function (dt, i18n) { 32557 return dt.i18n('searchBuilder.conditions.date.notEmpty', i18n.conditions.date.notEmpty); 32558 }, 32559 init: Criteria.initNoValue, 32560 inputValue: function () { 32561 return; 32562 }, 32563 isInputValid: function () { 32564 return true; 32565 }, 32566 search: function (value) { 32567 return !(value === null || value === undefined || value.length === 0); 32568 } 32569 } 32570 }; 32571 // The order of the conditions will make eslint sad :( 32572 // Has to be in this order so that they are displayed correctly in select elements 32573 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32574 // eslint-disable-next-line @typescript-eslint/member-ordering 32575 Criteria.numConditions = { 32576 '=': { 32577 conditionName: function (dt, i18n) { 32578 return dt.i18n('searchBuilder.conditions.number.equals', i18n.conditions.number.equals); 32579 }, 32580 init: Criteria.initSelect, 32581 inputValue: Criteria.inputValueSelect, 32582 isInputValid: Criteria.isInputValidSelect, 32583 search: function (value, comparison) { 32584 return +value === +comparison[0]; 32585 } 32586 }, 32587 // eslint-disable-next-line sort-keys 32588 '!=': { 32589 conditionName: function (dt, i18n) { 32590 return dt.i18n('searchBuilder.conditions.number.not', i18n.conditions.number.not); 32591 }, 32592 init: Criteria.initSelect, 32593 inputValue: Criteria.inputValueSelect, 32594 isInputValid: Criteria.isInputValidSelect, 32595 search: function (value, comparison) { 32596 return +value !== +comparison[0]; 32597 } 32598 }, 32599 '<': { 32600 conditionName: function (dt, i18n) { 32601 return dt.i18n('searchBuilder.conditions.number.lt', i18n.conditions.number.lt); 32602 }, 32603 init: Criteria.initInput, 32604 inputValue: Criteria.inputValueInput, 32605 isInputValid: Criteria.isInputValidInput, 32606 search: function (value, comparison) { 32607 return +value < +comparison[0]; 32608 } 32609 }, 32610 '<=': { 32611 conditionName: function (dt, i18n) { 32612 return dt.i18n('searchBuilder.conditions.number.lte', i18n.conditions.number.lte); 32613 }, 32614 init: Criteria.initInput, 32615 inputValue: Criteria.inputValueInput, 32616 isInputValid: Criteria.isInputValidInput, 32617 search: function (value, comparison) { 32618 return +value <= +comparison[0]; 32619 } 32620 }, 32621 '>=': { 32622 conditionName: function (dt, i18n) { 32623 return dt.i18n('searchBuilder.conditions.number.gte', i18n.conditions.number.gte); 32624 }, 32625 init: Criteria.initInput, 32626 inputValue: Criteria.inputValueInput, 32627 isInputValid: Criteria.isInputValidInput, 32628 search: function (value, comparison) { 32629 return +value >= +comparison[0]; 32630 } 32631 }, 32632 // eslint-disable-next-line sort-keys 32633 '>': { 32634 conditionName: function (dt, i18n) { 32635 return dt.i18n('searchBuilder.conditions.number.gt', i18n.conditions.number.gt); 32636 }, 32637 init: Criteria.initInput, 32638 inputValue: Criteria.inputValueInput, 32639 isInputValid: Criteria.isInputValidInput, 32640 search: function (value, comparison) { 32641 return +value > +comparison[0]; 32642 } 32643 }, 32644 'between': { 32645 conditionName: function (dt, i18n) { 32646 return dt.i18n('searchBuilder.conditions.number.between', i18n.conditions.number.between); 32647 }, 32648 init: Criteria.init2Input, 32649 inputValue: Criteria.inputValueInput, 32650 isInputValid: Criteria.isInputValidInput, 32651 search: function (value, comparison) { 32652 if (+comparison[0] < +comparison[1]) { 32653 return +comparison[0] <= +value && +value <= +comparison[1]; 32654 } 32655 else { 32656 return +comparison[1] <= +value && +value <= +comparison[0]; 32657 } 32658 } 32659 }, 32660 // eslint-disable-next-line sort-keys 32661 '!between': { 32662 conditionName: function (dt, i18n) { 32663 return dt.i18n('searchBuilder.conditions.number.notBetween', i18n.conditions.number.notBetween); 32664 }, 32665 init: Criteria.init2Input, 32666 inputValue: Criteria.inputValueInput, 32667 isInputValid: Criteria.isInputValidInput, 32668 search: function (value, comparison) { 32669 if (+comparison[0] < +comparison[1]) { 32670 return !(+comparison[0] <= +value && +value <= +comparison[1]); 32671 } 32672 else { 32673 return !(+comparison[1] <= +value && +value <= +comparison[0]); 32674 } 32675 } 32676 }, 32677 'null': { 32678 conditionName: function (dt, i18n) { 32679 return dt.i18n('searchBuilder.conditions.number.empty', i18n.conditions.number.empty); 32680 }, 32681 init: Criteria.initNoValue, 32682 inputValue: function () { 32683 return; 32684 }, 32685 isInputValid: function () { 32686 return true; 32687 }, 32688 search: function (value) { 32689 return value === null || value === undefined || value.length === 0; 32690 } 32691 }, 32692 // eslint-disable-next-line sort-keys 32693 '!null': { 32694 conditionName: function (dt, i18n) { 32695 return dt.i18n('searchBuilder.conditions.number.notEmpty', i18n.conditions.number.notEmpty); 32696 }, 32697 init: Criteria.initNoValue, 32698 inputValue: function () { 32699 return; 32700 }, 32701 isInputValid: function () { 32702 return true; 32703 }, 32704 search: function (value) { 32705 return !(value === null || value === undefined || value.length === 0); 32706 } 32707 } 32708 }; 32709 // The order of the conditions will make eslint sad :( 32710 // Has to be in this order so that they are displayed correctly in select elements 32711 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32712 // eslint-disable-next-line @typescript-eslint/member-ordering 32713 Criteria.numFmtConditions = { 32714 '=': { 32715 conditionName: function (dt, i18n) { 32716 return dt.i18n('searchBuilder.conditions.number.equals', i18n.conditions.number.equals); 32717 }, 32718 init: Criteria.initSelect, 32719 inputValue: Criteria.inputValueSelect, 32720 isInputValid: Criteria.isInputValidSelect, 32721 search: function (value, comparison) { 32722 return Criteria.parseNumFmt(value) === Criteria.parseNumFmt(comparison[0]); 32723 } 32724 }, 32725 // eslint-disable-next-line sort-keys 32726 '!=': { 32727 conditionName: function (dt, i18n) { 32728 return dt.i18n('searchBuilder.conditions.number.not', i18n.conditions.number.not); 32729 }, 32730 init: Criteria.initSelect, 32731 inputValue: Criteria.inputValueSelect, 32732 isInputValid: Criteria.isInputValidSelect, 32733 search: function (value, comparison) { 32734 return Criteria.parseNumFmt(value) !== Criteria.parseNumFmt(comparison[0]); 32735 } 32736 }, 32737 '<': { 32738 conditionName: function (dt, i18n) { 32739 return dt.i18n('searchBuilder.conditions.number.lt', i18n.conditions.number.lt); 32740 }, 32741 init: Criteria.initInput, 32742 inputValue: Criteria.inputValueInput, 32743 isInputValid: Criteria.isInputValidInput, 32744 search: function (value, comparison) { 32745 return Criteria.parseNumFmt(value) < Criteria.parseNumFmt(comparison[0]); 32746 } 32747 }, 32748 '<=': { 32749 conditionName: function (dt, i18n) { 32750 return dt.i18n('searchBuilder.conditions.number.lte', i18n.conditions.number.lte); 32751 }, 32752 init: Criteria.initInput, 32753 inputValue: Criteria.inputValueInput, 32754 isInputValid: Criteria.isInputValidInput, 32755 search: function (value, comparison) { 32756 return Criteria.parseNumFmt(value) <= Criteria.parseNumFmt(comparison[0]); 32757 } 32758 }, 32759 '>=': { 32760 conditionName: function (dt, i18n) { 32761 return dt.i18n('searchBuilder.conditions.number.gte', i18n.conditions.number.gte); 32762 }, 32763 init: Criteria.initInput, 32764 inputValue: Criteria.inputValueInput, 32765 isInputValid: Criteria.isInputValidInput, 32766 search: function (value, comparison) { 32767 return Criteria.parseNumFmt(value) >= Criteria.parseNumFmt(comparison[0]); 32768 } 32769 }, 32770 // eslint-disable-next-line sort-keys 32771 '>': { 32772 conditionName: function (dt, i18n) { 32773 return dt.i18n('searchBuilder.conditions.number.gt', i18n.conditions.number.gt); 32774 }, 32775 init: Criteria.initInput, 32776 inputValue: Criteria.inputValueInput, 32777 isInputValid: Criteria.isInputValidInput, 32778 search: function (value, comparison) { 32779 return Criteria.parseNumFmt(value) > Criteria.parseNumFmt(comparison[0]); 32780 } 32781 }, 32782 'between': { 32783 conditionName: function (dt, i18n) { 32784 return dt.i18n('searchBuilder.conditions.number.between', i18n.conditions.number.between); 32785 }, 32786 init: Criteria.init2Input, 32787 inputValue: Criteria.inputValueInput, 32788 isInputValid: Criteria.isInputValidInput, 32789 search: function (value, comparison) { 32790 var val = Criteria.parseNumFmt(value); 32791 var comp0 = Criteria.parseNumFmt(comparison[0]); 32792 var comp1 = Criteria.parseNumFmt(comparison[1]); 32793 if (+comp0 < +comp1) { 32794 return +comp0 <= +val && +val <= +comp1; 32795 } 32796 else { 32797 return +comp1 <= +val && +val <= +comp0; 32798 } 32799 } 32800 }, 32801 // eslint-disable-next-line sort-keys 32802 '!between': { 32803 conditionName: function (dt, i18n) { 32804 return dt.i18n('searchBuilder.conditions.number.notBetween', i18n.conditions.number.notBetween); 32805 }, 32806 init: Criteria.init2Input, 32807 inputValue: Criteria.inputValueInput, 32808 isInputValid: Criteria.isInputValidInput, 32809 search: function (value, comparison) { 32810 var val = Criteria.parseNumFmt(value); 32811 var comp0 = Criteria.parseNumFmt(comparison[0]); 32812 var comp1 = Criteria.parseNumFmt(comparison[1]); 32813 if (+comp0 < +comp1) { 32814 return !(+comp0 <= +val && +val <= +comp1); 32815 } 32816 else { 32817 return !(+comp1 <= +val && +val <= +comp0); 32818 } 32819 } 32820 }, 32821 'null': { 32822 conditionName: function (dt, i18n) { 32823 return dt.i18n('searchBuilder.conditions.number.empty', i18n.conditions.number.empty); 32824 }, 32825 init: Criteria.initNoValue, 32826 inputValue: function () { 32827 return; 32828 }, 32829 isInputValid: function () { 32830 return true; 32831 }, 32832 search: function (value) { 32833 return value === null || value === undefined || value.length === 0; 32834 } 32835 }, 32836 // eslint-disable-next-line sort-keys 32837 '!null': { 32838 conditionName: function (dt, i18n) { 32839 return dt.i18n('searchBuilder.conditions.number.notEmpty', i18n.conditions.number.notEmpty); 32840 }, 32841 init: Criteria.in
32841itNoValue, 32842 inputValue: function () { 32843 return; 32844 }, 32845 isInputValid: function () { 32846 return true; 32847 }, 32848 search: function (value) { 32849 return !(value === null || value === undefined || value.length === 0); 32850 } 32851 } 32852 }; 32853 // The order of the conditions will make eslint sad :( 32854 // Has to be in this order so that they are displayed correctly in select elements 32855 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32856 // eslint-disable-next-line @typescript-eslint/member-ordering 32857 Criteria.stringConditions = { 32858 '=': { 32859 conditionName: function (dt, i18n) { 32860 return dt.i18n('searchBuilder.conditions.string.equals', i18n.conditions.string.equals); 32861 }, 32862 init: Criteria.initSelect, 32863 inputValue: Criteria.inputValueSelect, 32864 isInputValid: Criteria.isInputValidSelect, 32865 search: function (value, comparison) { 32866 return value === comparison[0]; 32867 } 32868 }, 32869 // eslint-disable-next-line sort-keys 32870 '!=': { 32871 conditionName: function (dt, i18n) { 32872 return dt.i18n('searchBuilder.conditions.string.not', i18n.conditions.string.not); 32873 }, 32874 init: Criteria.initSelect, 32875 inputValue: Criteria.inputValueSelect, 32876 isInputValid: Criteria.isInputValidInput, 32877 search: function (value, comparison) { 32878 return value !== comparison[0]; 32879 } 32880 }, 32881 'starts': { 32882 conditionName: function (dt, i18n) { 32883 return dt.i18n('searchBuilder.conditions.string.startsWith', i18n.conditions.string.startsWith); 32884 }, 32885 init: Criteria.initInput, 32886 inputValue: Criteria.inputValueInput, 32887 isInputValid: Criteria.isInputValidInput, 32888 search: function (value, comparison) { 32889 return value.toLowerCase().indexOf(comparison[0].toLowerCase()) === 0; 32890 } 32891 }, 32892 // eslint-disable-next-line sort-keys 32893 '!starts': { 32894 conditionName: function (dt, i18n) { 32895 return dt.i18n('searchBuilder.conditions.string.notStartsWith', i18n.conditions.string.notStartsWith); 32896 }, 32897 init: Criteria.initInput, 32898 inputValue: Criteria.inputValueInput, 32899 isInputValid: Criteria.isInputValidInput, 32900 search: function (value, comparison) { 32901 return value.toLowerCase().indexOf(comparison[0].toLowerCase()) !== 0; 32902 } 32903 }, 32904 // eslint-disable-next-line sort-keys 32905 'contains': { 32906 conditionName: function (dt, i18n) { 32907 return dt.i18n('searchBuilder.conditions.string.contains', i18n.conditions.string.contains); 32908 }, 32909 init: Criteria.initInput, 32910 inputValue: Criteria.inputValueInput, 32911 isInputValid: Criteria.isInputValidInput, 32912 search: function (value, comparison) { 32913 return value.toLowerCase().includes(comparison[0].toLowerCase()); 32914 } 32915 }, 32916 // eslint-disable-next-line sort-keys 32917 '!contains': { 32918 conditionName: function (dt, i18n) { 32919 return dt.i18n('searchBuilder.conditions.string.notContains', i18n.conditions.string.notContains); 32920 }, 32921 init: Criteria.initInput, 32922 inputValue: Criteria.inputValueInput, 32923 isInputValid: Criteria.isInputValidInput, 32924 search: function (value, comparison) { 32925 return !value.toLowerCase().includes(comparison[0].toLowerCase()); 32926 } 32927 }, 32928 'ends': { 32929 conditionName: function (dt, i18n) { 32930 return dt.i18n('searchBuilder.conditions.string.endsWith', i18n.conditions.string.endsWith); 32931 }, 32932 init: Criteria.initInput, 32933 inputValue: Criteria.inputValueInput, 32934 isInputValid: Criteria.isInputValidInput, 32935 search: function (value, comparison) { 32936 return value.toLowerCase().endsWith(comparison[0].toLowerCase()); 32937 } 32938 }, 32939 // eslint-disable-next-line sort-keys 32940 '!ends': { 32941 conditionName: function (dt, i18n) { 32942 return dt.i18n('searchBuilder.conditions.string.notEndsWith', i18n.conditions.string.notEndsWith); 32943 }, 32944 init: Criteria.initInput, 32945 inputValue: Criteria.inputValueInput, 32946 isInputValid: Criteria.isInputValidInput, 32947 search: function (value, comparison) { 32948 return !value.toLowerCase().endsWith(comparison[0].toLowerCase()); 32949 } 32950 }, 32951 'null': { 32952 conditionName: function (dt, i18n) { 32953 return dt.i18n('searchBuilder.conditions.string.empty', i18n.conditions.string.empty); 32954 }, 32955 init: Criteria.initNoValue, 32956 inputValue: function () { 32957 return; 32958 }, 32959 isInputValid: function () { 32960 return true; 32961 }, 32962 search: function (value) { 32963 return value === null || value === undefined || value.length === 0; 32964 } 32965 }, 32966 // eslint-disable-next-line sort-keys 32967 '!null': { 32968 conditionName: function (dt, i18n) { 32969 return dt.i18n('searchBuilder.conditions.string.notEmpty', i18n.conditions.string.notEmpty); 32970 }, 32971 init: Criteria.initNoValue, 32972 inputValue: function () { 32973 return; 32974 }, 32975 isInputValid: function () { 32976 return true; 32977 }, 32978 search: function (value) { 32979 return !(value === null || value === undefined || value.length === 0); 32980 } 32981 } 32982 }; 32983 // The order of the conditions will make eslint sad :( 32984 // Also have to disable member ordering for this as the private methods used are not yet declared otherwise 32985 // eslint-disable-next-line @typescript-eslint/member-ordering 32986 Criteria.arrayConditions = { 32987 'contains': { 32988 conditionName: function (dt, i18n) { 32989 return dt.i18n('searchBuilder.conditions.array.contains', i18n.conditions.array.contains); 32990 }, 32991 init: Criteria.initSelectArray, 32992 inputValue: Criteria.inputValueSelect, 32993 isInputValid: Criteria.isInputValidSelect, 32994 search: function (value, comparison) { 32995 return value.includes(comparison[0]); 32996 } 32997 }, 32998 'without': { 32999 conditionName: function (dt, i18n) { 33000 return dt.i18n('searchBuilder.conditions.array.without', i18n.conditions.array.without); 33001 }, 33002 init: Criteria.initSelectArray, 33003 inputValue: Criteria.inputValueSelect, 33004 isInputValid: Criteria.isInputValidSelect, 33005 search: function (value, comparison) { 33006 return value.indexOf(comparison[0]) === -1; 33007 } 33008 }, 33009 // eslint-disable-next-line sort-keys 33010 '=': { 33011 conditionName: function (dt, i18n) { 33012 return dt.i18n('searchBuilder.conditions.array.equals', i18n.conditions.array.equals); 33013 }, 33014 init: Criteria.initSelect, 33015 inputValue: Criteria.inputValueSelect, 33016 isInputValid: Criteria.isInputValidSelect, 33017 search: function (value, comparison) { 33018 if (value.length === comparison[0].length) { 33019 for (var i = 0; i < value.length; i++) { 33020 if (value[i] !== comparison[0][i]) { 33021 return false; 33022 } 33023 } 33024 return true; 33025 } 33026 return false; 33027 } 33028 }, 33029 // eslint-disable-next-line sort-keys 33030 '!=': { 33031 conditionName: function (dt, i18n) { 33032 return dt.i18n('searchBuilder.conditions.array.not', i18n.conditions.array.not); 33033 }, 33034 init: Criteria.initSelect, 33035 inputValue: Criteria.inputValueSelect, 33036 isInputValid: Criteria.isInputValidSelect, 33037 search: function (value, comparison) { 33038 if (value.length === comparison[0].length) { 33039 for (var i = 0; i < value.length; i++) { 33040 if (value[i] !== comparison[0][i]) { 33041 return true; 33042 } 33043 } 33044 return false; 33045 } 33046 return true; 33047 } 33048 }, 33049 'null': { 33050 conditionName: function (dt, i18n) { 33051 return dt.i18n('searchBuilder.conditions.array.empty', i18n.conditions.array.empty); 33052 }, 33053 init: Criteria.initNoValue, 33054 inputValue: function () { 33055 return; 33056 }, 33057 isInputValid: function () { 33058 return true; 33059 }, 33060 search: function (value) { 33061 return value === null || value === undefined || value.length === 0; 33062 } 33063 }, 33064 // eslint-disable-next-line sort-keys 33065 '!null': { 33066 conditionName: function (dt, i18n) { 33067 return dt.i18n('searchBuilder.conditions.array.notEmpty', i18n.conditions.array.notEmpty); 33068 }, 33069 init: Criteria.initNoValue, 33070 inputValue: function () { 33071 return; 33072 }, 33073 isInputValid: function () { 33074 return true; 33075 }, 33076 search: function (value) { 33077 return value !== null && value !== undefined && value.length !== 0; 33078 } 33079 } 33080 }; 33081 // eslint will be sad because we have to disable member ordering for this as the 33082 // private static properties used are not yet declared otherwise 33083 // eslint-disable-next-line @typescript-eslint/member-ordering 33084 Criteria.defaults = { 33085 columns: true, 33086 conditions: { 33087 'array': Criteria.arrayConditions, 33088 'date': Criteria.dateConditions, 33089 'html': Criteria.stringConditions, 33090 'html-num': Criteria.numConditions, 33091 'html-num-fmt': Criteria.numFmtConditions, 33092 'luxon': Criteria.luxonDateConditions, 33093 'moment': Criteria.momentDateConditions, 33094 'num': Criteria.numConditions, 33095 'num-fmt': Criteria.numFmtConditions, 33096 'string': Criteria.stringConditions 33097 }, 33098 depthLimit: false, 33099 enterSearch: false, 33100 filterChanged: undefined, 33101 greyscale: false, 33102 i18n: { 33103 add: 'Add Condition', 33104 button: { 33105 0: 'Search Builder', 33106 _: 'Search Builder (%d)' 33107 }, 33108 clearAll: 'Clear All', 33109 condition: 'Condition', 33110 data: 'Data', 33111 "delete": '×', 33112 deleteTitle: 'Delete filtering rule',
33113 left: '<', 33114 leftTitle: 'Outdent criteria', 33115 logicAnd: 'And', 33116 logicOr: 'Or', 33117 right: '>', 33118 rightTitle: 'Indent criteria', 33119 title: { 33120 0: 'Custom Search Builder', 33121 _: 'Custom Search Builder (%d)' 33122 }, 33123 value: 'Value', 33124 valueJoiner: 'and' 33125 }, 33126 logic: 'AND', 33127 orthogonal: { 33128 display: 'display', 33129 search: 'filter' 33130 }, 33131 preDefined: false 33132 }; 33133 return Criteria; 33134 }()); 33135 33136 var $$2; 33137 var dataTable$2; 33138 /** 33139 * Sets the value of jQuery for use in the file 33140 * 33141 * @param jq the instance of jQuery to be set 33142 */ 33143 function setJQuery$1(jq) { 33144 $$2 = jq; 33145 dataTable$2 = jq.fn.dataTable; 33146 } 33147 /** 33148 * The Group class is used within SearchBuilder to represent a group of criteria 33149 */ 33150 var Group = /** @class */ (function () { 33151 function Group(table, opts, topGroup, index, isChild, depth, serverData) { 33152 if (index === void 0) { index = 0; } 33153 if (isChild === void 0) { isChild = false; } 33154 if (depth === void 0) { depth = 1; } 33155 if (serverData === void 0) { serverData = undefined; } 33156 // Check that the required version of DataTables is included 33157 if (!dataTable$2 || !dataTable$2.versionCheck || !dataTable$2.versionCheck('1.10.0')) { 33158 throw new Error('SearchBuilder requires DataTables 1.10 or newer'); 33159 } 33160 this.classes = $$2.extend(true, {}, Group.classes); 33161 // Get options from user 33162 this.c = $$2.extend(true, {}, Group.defaults, opts); 33163 this.s = { 33164 criteria: [], 33165 depth: depth, 33166 dt: table, 33167 index: index, 33168 isChild: isChild, 33169 logic: undefined, 33170 opts: opts, 33171 preventRedraw: false, 33172 serverData: serverData, 33173 toDrop: undefined, 33174 topGroup: topGroup 33175 }; 33176 this.dom = { 33177 add: $$2('<button/>') 33178 .addClass(this.classes.add) 33179 .addClass(this.classes.button) 33180 .attr('type', 'button'), 33181 clear: $$2('<button>×</button>') 33182 .addClass(this.classes.button) 33183 .addClass(this.classes.clearGroup) 33184 .attr('type', 'button'), 33185 container: $$2('<div/>') 33186 .addClass(this.classes.group), 33187 logic: $$2('<button><div/></button>') 33188 .addClass(this.classes.logic) 33189 .addClass(this.classes.button) 33190 .attr('type', 'button'), 33191 logicContainer: $$2('<div/>') 33192 .addClass(this.classes.logicContainer) 33193 }; 33194 // A reference to the top level group is maintained throughout any subgroups and criteria that may be created 33195 if (this.s.topGroup === undefined) { 33196 this.s.topGroup = this.dom.container; 33197 } 33198 this._setup(); 33199 return this; 33200 } 33201 /** 33202 * Destroys the groups buttons, clears the internal criteria and removes it from the dom 33203 */ 33204 Group.prototype.destroy = function () { 33205 // Turn off listeners 33206 this.dom.add.off('.dtsb'); 33207 this.dom.logic.off('.dtsb'); 33208 // Trigger event for groups at a higher level to pick up on 33209 this.dom.container 33210 .trigger('dtsb-destroy') 33211 .remove(); 33212 this.s.criteria = []; 33213 }; 33214 /** 33215 * Gets the details required to rebuild the group 33216 */ 33217 // Eslint upset at empty object but needs to be done 33218 // eslint-disable-next-line @typescript-eslint/ban-types 33219 Group.prototype.getDetails = function (deFormatDates) { 33220 if (deFormatDates === void 0) { deFormatDates = false; } 33221 if (this.s.criteria.length === 0) { 33222 return {}; 33223 } 33224 var details = { 33225 criteria: [], 33226 logic: this.s.logic 33227 }; 33228 // NOTE here crit could be either a subgroup or a criteria 33229 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33230 var crit = _a[_i]; 33231 details.criteria.push(crit.criteria.getDetails(deFormatDates)); 33232 } 33233 return details; 33234 }; 33235 /** 33236 * Getter for the node for the container of the group 33237 * 33238 * @returns Node for the container of the group 33239 */ 33240 Group.prototype.getNode = function () { 33241 return this.dom.container; 33242 }; 33243 /** 33244 * Rebuilds the group based upon the details passed in 33245 * 33246 * @param loadedDetails the details required to rebuild the group 33247 */ 33248 Group.prototype.rebuild = function (loadedDetails) { 33249 // If no criteria are stored then just return 33250 if (loadedDetails.criteria === undefined || 33251 loadedDetails.criteria === null || 33252 Array.isArray(loadedDetails.criteria) && loadedDetails.criteria.length === 0) { 33253 return; 33254 } 33255 this.s.logic = loadedDetails.logic; 33256 this.dom.logic.children().first().html(this.s.logic === 'OR' 33257 ? this.s.dt.i18n('searchBuilder.logicOr', this.c.i18n.logicOr) 33258 : this.s.dt.i18n('searchBuilder.logicAnd', this.c.i18n.logicAnd)); 33259 // Add all of the criteria, be it a sub group or a criteria 33260 if (Array.isArray(loadedDetails.criteria)) { 33261 for (var _i = 0, _a = loadedDetails.criteria; _i < _a.length; _i++) { 33262 var crit = _a[_i]; 33263 if (crit.logic !== undefined) { 33264 this._addPrevGroup(crit); 33265 } 33266 else if (crit.logic === undefined) { 33267 this._addPrevCriteria(crit); 33268 } 33269 } 33270 } 33271 // For all of the criteria children, update the arrows incase they require changing and set the listeners 33272 for (var _b = 0, _c = this.s.criteria; _b < _c.length; _b++) { 33273 var crit = _c[_b]; 33274 if (crit.criteria instanceof Criteria) { 33275 crit.criteria.updateArrows(this.s.criteria.length > 1); 33276 this._setCriteriaListeners(crit.criteria); 33277 } 33278 } 33279 }; 33280 /** 33281 * Redraws the Contents of the searchBuilder Groups and Criteria 33282 */ 33283 Group.prototype.redrawContents = function () { 33284 if (this.s.preventRedraw) { 33285 return; 33286 } 33287 // Clear the container out and add the basic elements 33288 this.dom.container.children().detach(); 33289 this.dom.container 33290 .append(this.dom.logicContainer) 33291 .append(this.dom.add); 33292 // Sort the criteria by index so that they appear in the correct order 33293 this.s.criteria.sort(function (a, b) { 33294 if (a.criteria.s.index < b.criteria.s.index) { 33295 return -1; 33296 } 33297 else if (a.criteria.s.index > b.criteria.s.index) { 33298 return 1; 33299 } 33300 return 0; 33301 }); 33302 this.setListeners(); 33303 for (var i = 0; i < this.s.criteria.length; i++) { 33304 var crit = this.s.criteria[i].criteria; 33305 if (crit instanceof Criteria) { 33306 // Reset the index to the new value 33307 this.s.criteria[i].index = i; 33308 this.s.criteria[i].criteria.s.index = i; 33309 // Add to the group 33310 this.s.criteria[i].criteria.dom.container.insertBefore(this.dom.add); 33311 // Set listeners for various points 33312 this._setCriteriaListeners(crit); 33313 this.s.criteria[i].criteria.s.preventRedraw = this.s.preventRedraw; 33314 this.s.criteria[i].criteria.rebuild(this.s.criteria[i].criteria.getDetails()); 33315 this.s.criteria[i].criteria.s.preventRedraw = false; 33316 } 33317 else if (crit instanceof Group && crit.s.criteria.length > 0) { 33318 // Reset the index to the new value 33319 this.s.criteria[i].index = i; 33320 this.s.criteria[i].criteria.s.index = i; 33321 // Add the sub group to the group 33322 this.s.criteria[i].criteria.dom.container.insertBefore(this.dom.add); 33323 // Redraw the contents of the group 33324 crit.s.preventRedraw = this.s.preventRedraw; 33325 crit.redrawContents(); 33326 crit.s.preventRedraw = false; 33327 this._setGroupListeners(crit); 33328 } 33329 else { 33330 // The group is empty so remove it 33331 this.s.criteria.splice(i, 1); 33332 i--; 33333 } 33334 } 33335 this.setupLogic(); 33336 }; 33337 /** 33338 * Resizes the logic button only rather than the entire dom. 33339 */ 33340 Group.prototype.redrawLogic = function () { 33341 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33342 var crit = _a[_i]; 33343 if (crit.criteria instanceof Group) { 33344 crit.criteria.redrawLogic(); 33345 } 33346 } 33347 this.setupLogic(); 33348 }; 33349 /** 33350 * Search method, checking the row data against the criteria in the group 33351 * 33352 * @param rowData The row data to be compared 33353 * @returns boolean The result of the search 33354 */ 33355 Group.prototype.search = function (rowData, rowIdx) { 33356 if (this.s.logic === 'AND') { 33357 return this._andSearch(rowData, rowIdx); 33358 } 33359 else if (this.s.logic === 'OR') { 33360 return this._orSearch(rowData, rowIdx); 33361 } 33362 return true; 33363 }; 33364 /** 33365 * Locates the groups logic button to the correct location on the page 33366 */ 33367 Group.prototype.setupLogic = function () { 33368 // Remove logic button 33369 this.dom.logicContainer.remove(); 33370 this.dom.clear.remove(); 33371 // If there are no criteria in the group then keep the logic removed and return 33372 if (this.s.criteria.length < 1) { 33373 if (!this.s.isChild) { 33374 this.dom.container.trigger('dtsb-destroy'); 33375 // Set criteria left margin 33376 this.dom.container.css('margin-left', 0); 33377 } 33378 return; 33379 } 33380 this.dom.clear.height('0px'); 33381 this.dom.logicContainer.append(this.dom.clear); 33382 // Prepend logic button 33383 this.dom.container.prepend(this.dom.logicContainer); 33384 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33385 var crit = _a[_i]; 33386 if (crit.criteria instanceof Criteria) { 33387 crit.criteria.setupButtons(); 33388 } 33389 } 33390 // Set width, take 2 for the border 33391 var height = this.dom.container.outerHeight() - 1; 33392 this.dom.logicContainer.width(height); 33393 this._setLogicListener(); 33394 // Set criteria left margin 33395 this.dom.container.css('margin-left', this.dom.logicContainer.outerHeight(true)); 33396 var logicOffset = this.dom.logicContainer.offset(); 33397 // Set horizontal alignment 33398 var currentLeft = logicOffset.left; 33399 var groupLeft = this.dom.container.offset().left; 33400 var shuffleLeft = currentLeft - groupLeft; 33401 var newPos = currentLeft - shuffleLeft - this.dom.logicContainer.outerHeight(true); 33402 this.dom.logicContainer.offset({ left: newPos }); 33403 // Set vertical alignment 33404 var firstCrit = this.dom.logicContainer.next(); 33405 var currentTop = logicOffset.top; 33406 var firstTop = $$2(firstCrit).offset().top; 33407 var shuffleTop = currentTop - firstTop; 33408 var newTop = currentTop - shuffleTop; 33409 this.dom.logicContainer.offset({ top: newTop }); 33410 this.dom.clear.outerHeight(this.dom.logicContainer.height()); 33411 this._setClearListener(); 33412 }; 33413 /** 33414 * Sets listeners on the groups elements 33415 */ 33416 Group.prototype.setListeners = function () { 33417 var _this = this; 33418 this.dom.add.unbind('click'); 33419 this.dom.add.on('click.dtsb', function () { 33420 // If this is the parent group then the logic button has not been added yet 33421 if (!_this.s.isChild) { 33422 _this.dom.container.prepend(_this.dom.logicContainer); 33423 } 33424 _this.addCriteria(); 33425 _this.dom.container.trigger('dtsb-add'); 33426 _this.s.dt.state.save(); 33427 return false; 33428 }); 33429 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33430 var crit = _a[_i]; 33431 crit.criteria.setListeners(); 33432 } 33433 this._setClearListener(); 33434 this._setLogicListener(); 33435 }; 33436 /** 33437 * Adds a criteria to the group 33438 * 33439 * @param crit Instance of Criteria to be added to the group 33440 */ 33441 Group.prototype.addCriteria = function (crit) { 33442 if (crit === void 0) { crit = null; } 33443 var index = crit === null ? this.s.criteria.length : crit.s.index; 33444 var criteria = new Criteria(this.s.dt, this.s.opts, this.s.topGroup, index, this.s.depth, this.s.serverData); 33445 // If a Criteria has been passed in then set the values to continue that 33446 if (crit !== null) { 33447 criteria.c = crit.c; 33448 criteria.s = crit.s; 33449 criteria.s.depth = this.s.depth; 33450 criteria.classes = crit.classes; 33451 } 33452 criteria.populate(); 33453 var inserted = false; 33454 for (var i = 0; i < this.s.criteria.length; i++) { 33455 if (i === 0 && this.s.criteria[i].criteria.s.index > criteria.s.index) { 33456 // Add the node for the criteria at the start of the group 33457 criteria.getNode().insertBefore(this.s.criteria[i].criteria.dom.container); 33458 inserted = true; 33459 } 33460 else if (i < this.s.criteria.length - 1 && 33461 this.s.criteria[i].criteria.s.index < criteria.s.index && 33462 this.s.criteria[i + 1].criteria.s.index > criteria.s.index) { 33463 // Add the node for the criteria in the correct location 33464 criteria.getNode().insertAfter(this.s.criteria[i].criteria.dom.container); 33465 inserted = true; 33466 } 33467 } 33468 if (!inserted) { 33469 criteria.getNode().insertBefore(this.dom.add); 33470 } 33471 // Add the details for this criteria to the array 33472 this.s.criteria.push({ 33473 criteria: criteria, 33474 index: index 33475 }); 33476 this.s.criteria = this.s.criteria.sort(function (a, b) { return a.criteria.s.index - b.criteria.s.index; }); 33477 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33478 var opt = _a[_i]; 33479 if (opt.criteria instanceof Criteria) { 33480 opt.criteria.updateArrows(this.s.criteria.length > 1); 33481 } 33482
33482 } 33483 this._setCriteriaListeners(criteria); 33484 criteria.setListeners(); 33485 this.setupLogic(); 33486 }; 33487 /** 33488 * Checks the group to see if it has any filled criteria 33489 */ 33490 Group.prototype.checkFilled = function () { 33491 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33492 var crit = _a[_i]; 33493 if (crit.criteria instanceof Criteria && crit.criteria.s.filled || 33494 crit.criteria instanceof Group && crit.criteria.checkFilled()) { 33495 return true; 33496 } 33497 } 33498 return false; 33499 }; 33500 /** 33501 * Gets the count for the number of criteria in this group and any sub groups 33502 */ 33503 Group.prototype.count = function () { 33504 var count = 0; 33505 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33506 var crit = _a[_i]; 33507 if (crit.criteria instanceof Group) { 33508 count += crit.criteria.count(); 33509 } 33510 else { 33511 count++; 33512 } 33513 } 33514 return count; 33515 }; 33516 /** 33517 * Rebuilds a sub group that previously existed 33518 * 33519 * @param loadedGroup The details of a group within this group 33520 */ 33521 Group.prototype._addPrevGroup = function (loadedGroup) { 33522 var idx = this.s.criteria.length; 33523 var group = new Group(this.s.dt, this.c, this.s.topGroup, idx, true, this.s.depth + 1, this.s.serverData); 33524 // Add the new group to the criteria array 33525 this.s.criteria.push({ 33526 criteria: group, 33527 index: idx, 33528 logic: group.s.logic 33529 }); 33530 // Rebuild it with the previous conditions for that group 33531 group.rebuild(loadedGroup); 33532 this.s.criteria[idx].criteria = group; 33533 this.s.topGroup.trigger('dtsb-redrawContents'); 33534 this._setGroupListeners(group); 33535 }; 33536 /** 33537 * Rebuilds a criteria of this group that previously existed 33538 * 33539 * @param loadedCriteria The details of a criteria within the group 33540 */ 33541 Group.prototype._addPrevCriteria = function (loadedCriteria) { 33542 var idx = this.s.criteria.length; 33543 var criteria = new Criteria(this.s.dt, this.s.opts, this.s.topGroup, idx, this.s.depth, this.s.serverData); 33544 criteria.populate(); 33545 // Add the new criteria to the criteria array 33546 this.s.criteria.push({ 33547 criteria: criteria, 33548 index: idx 33549 }); 33550 // Rebuild it with the previous conditions for that criteria 33551 criteria.s.preventRedraw = this.s.preventRedraw; 33552 criteria.rebuild(loadedCriteria); 33553 criteria.s.preventRedraw = false; 33554 this.s.criteria[idx].criteria = criteria; 33555 if (!this.s.preventRedraw) { 33556 this.s.topGroup.trigger('dtsb-redrawContents'); 33557 } 33558 }; 33559 /** 33560 * Checks And the criteria using AND logic 33561 * 33562 * @param rowData The row data to be checked against the search criteria 33563 * @returns boolean The result of the AND search 33564 */ 33565 Group.prototype._andSearch = function (rowData, rowIdx) { 33566 // If there are no criteria then return true for this group 33567 if (this.s.criteria.length === 0) { 33568 return true; 33569 } 33570 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33571 var crit = _a[_i]; 33572 // If the criteria is not complete then skip it 33573 if (crit.criteria instanceof Criteria && !crit.criteria.s.filled) { 33574 continue; 33575 } 33576 // Otherwise if a single one fails return false 33577 else if (!crit.criteria.search(rowData, rowIdx)) { 33578 return false; 33579 } 33580 } 33581 // If we get to here then everything has passed, so return true for the group 33582 return true; 33583 }; 33584 /** 33585 * Checks And the criteria using OR logic 33586 * 33587 * @param rowData The row data to be checked against the search criteria
33588 * @returns boolean The result of the OR search 33589 */ 33590 Group.prototype._orSearch = function (rowData, rowIdx) { 33591 // If there are no criteria in the group then return true 33592 if (this.s.criteria.length === 0) { 33593 return true; 33594 } 33595 // This will check to make sure that at least one criteria in the group is complete 33596 var filledfound = false; 33597 for (var _i = 0, _a = this.s.criteria; _i < _a.length; _i++) { 33598 var crit = _a[_i]; 33599 if (crit.criteria instanceof Criteria && crit.criteria.s.filled) { 33600 // A completed criteria has been found so set the flag 33601 filledfound = true; 33602 // If the search passes then return true 33603 if (crit.criteria.search(rowData, rowIdx)) { 33604 return true; 33605 } 33606 } 33607 else if (crit.criteria instanceof Group && crit.criteria.checkFilled()) { 33608 filledfound = true; 33609 if (crit.criteria.search(rowData, rowIdx)) { 33610 return true; 33611 } 33612 } 33613 } 33614 // If we get here we need to return the inverse of filledfound, 33615 // as if any have been found and we are here then none have passed 33616 return !filledfound; 33617 }; 33618 /** 33619 * Removes a criteria from the group 33620 * 33621 * @param criteria The criteria instance to be removed 33622 */ 33623 Group.prototype._removeCriteria = function (criteria, group) { 33624 if (group === void 0) { group = false; } 33625 // If removing a criteria and there is only then then just destroy the group 33626 if (this.s.criteria.length <= 1 && this.s.isChild) { 33627 this.destroy(); 33628 } 33629 else { 33630 // Otherwise splice the given criteria out and redo the indexes 33631 var last = void 0; 33632 for (var i = 0; i < this.s.criteria.length; i++) { 33633 if (this.s.criteria[i].index === criteria.s.index && 33634 (!group || this.s.criteria[i].criteria instanceof Group)) { 33635 last = i; 33636 } 33637 } 33638 // We want to remove the last element with the desired index, as its replacement will be inserted before it 33639 if (last !== undefined) { 33640 this.s.criteria.splice(last, 1); 33641 } 33642 for (var i = 0; i < this.s.criteria.length; i++) { 33643 this.s.criteria[i].index = i; 33644 this.s.criteria[i].criteria.s.index = i; 33645 } 33646 } 33647 }; 33648 /** 33649 * Sets the listeners in group for a criteria 33650 * 33651 * @param criteria The criteria for the listeners to be set on 33652 */ 33653 Group.prototype._setCriteriaListeners = function (criteria) { 33654 var _this = this; 33655 criteria.dom["delete"] 33656 .unbind('click') 33657 .on('click.dtsb', function () { 33658 _this._removeCriteria(criteria); 33659 criteria.dom.container.remove(); 33660 for (var _i = 0, _a = _this.s.criteria; _i < _a.length; _i++) { 33661 var crit = _a[_i]; 33662 if (crit.criteria instanceof Criteria) { 33663 crit.criteria.updateArrows(_this.s.criteria.length > 1); 33664 } 33665 } 33666 criteria.destroy(); 33667 _this.s.dt.draw(); 33668 _this.s.topGroup.trigger('dtsb-redrawContents'); 33669 return false; 33670 }); 33671 criteria.dom.right 33672 .unbind('click') 33673 .on('click.dtsb', function () { 33674 var idx = criteria.s.index; 33675 var group = new Group(_this.s.dt, _this.s.opts, _this.s.topGroup, criteria.s.index, true, _this.s.depth + 1, _this.s.serverData); 33676 // Add the criteria that is to be moved to the new group 33677 group.addCriteria(criteria); 33678 // Update the details in the current groups criteria array 33679 _this.s.criteria[idx].criteria = group; 33680 _this.s.criteria[idx].logic = 'AND'; 33681 _this.s.topGroup.trigger('dtsb-redrawContents'); 33682 _this._setGroupListeners(group); 33683 return false; 33684 }); 33685 criteria.dom.left 33686 .unbind('click') 33687 .on('click.dtsb', function () { 33688 _this.s.toDrop = new Criteria(_this.s.dt, _this.s.opts, _this.s.topGroup, criteria.s.index, undefined, _this.s.serverData); 33689 _this.s.toDrop.s = criteria.s; 33690 _this.s.toDrop.c = criteria.c; 33691 _this.s.toDrop.classes = criteria.classes; 33692 _this.s.toDrop.populate(); 33693 // The dropCriteria event mutates the reference to the index so need to store it 33694 var index = _this.s.toDrop.s.index; 33695 _this.dom.container.trigger('dtsb-dropCriteria'); 33696 criteria.s.index = index; 33697 _this._removeCriteria(criteria); 33698 // By tracking the top level group we can directly trigger a redraw on it, 33699 // bubbling is also possible, but that is slow with deep levelled groups 33700 _this.s.topGroup.trigger('dtsb-redrawContents'); 33701 _this.s.dt.draw(); 33702 return false; 33703 }); 33704 }; 33705 /** 33706 * Set's the listeners for the group clear button 33707 */ 33708 Group.prototype._setClearListener = function () { 33709 var _this = this; 33710 this.dom.clear 33711 .unbind('click') 33712 .on('click.dtsb', function () { 33713 if (!_this.s.isChild) { 33714 _this.dom.container.trigger('dtsb-clearContents'); 33715 return false; 33716 } 33717 _this.destroy(); 33718 _this.s.topGroup.trigger('dtsb-redrawContents'); 33719 return false; 33720 }); 33721 }; 33722 /** 33723 * Sets listeners for sub groups of this group 33724 * 33725 * @param group The sub group that the listeners are to be set on 33726 */ 33727 Group.prototype._setGroupListeners = function (group) { 33728 var _this = this; 33729 // Set listeners for the new group 33730 group.dom.add 33731 .unbind('click') 33732 .on('click.dtsb', function () { 33733 _this.setupLogic(); 33734 _this.dom.container.trigger('dtsb-add'); 33735 return false; 33736 }); 33737 group.dom.container 33738 .unbind('dtsb-add') 33739 .on('dtsb-add.dtsb', function () { 33740 _this.setupLogic(); 33741 _this.dom.container.trigger('dtsb-add'); 33742 return false; 33743 }); 33744 group.dom.container 33745 .unbind('dtsb-destroy') 33746 .on('dtsb-destroy.dtsb', function () { 33747 _this._removeCriteria(group, true); 33748 group.dom.container.remove(); 33749 _this.setupLogic(); 33750 return false; 33751 }); 33752 group.dom.container 33753 .unbind('dtsb-dropCriteria') 33754 .on('dtsb-dropCriteria.dtsb', function () { 33755 var toDrop = group.s.toDrop; 33756 toDrop.s.index = group.s.index; 33757 toDrop.updateArrows(_this.s.criteria.length > 1); 33758 _this.addCriteria(toDrop); 33759 return false; 33760 }); 33761 group.setListeners(); 33762 }; 33763 /** 33764 * Sets up the Group instance, setting listeners and appending elements 33765 */ 33766 Group.prototype._setup = function () { 33767 this.setListeners(); 33768 this.dom.add.html(this.s.dt.i18n('searchBuilder.add', this.c.i18n.add)); 33769 this.dom.logic.children().first().html(this.c.logic === 'OR' 33770 ? this.s.dt.i18n('searchBuilder.logicOr', this.c.i18n.logicOr) 33771 : this.s.dt.i18n('searchBuilder.logicAnd', this.c.i18n.logicAnd)); 33772 this.s.logic = this.c.logic === 'OR' ? 'OR' : 'AND'; 33773 if (this.c.greyscale) { 33774 this.dom.logic.addClass(this.classes.greyscale); 33775 } 33776 this.dom.logicContainer.append(this.dom.logic).append(this.dom.clear); 33777 // Only append the logic button immediately if this is a sub group, 33778 // otherwise it will be prepended later when adding a criteria 33779 if (this.s.isChild) { 33780 this.dom.container.append(this.dom.logicContainer); 33781 } 33782 this.dom.container.append(this.dom.add); 33783 }; 33784 /** 33785 * Sets the listener for the logic button 33786 */ 33787 Group.prototype._setLogicListener = function () { 33788 var _this = this; 33789 this.dom.logic 33790 .unbind('click') 33791 .on('click.dtsb', function () { 33792 _this._toggleLogic(); 33793 _this.s.dt.draw(); 33794 for (var _i = 0, _a = _this.s.criteria; _i < _a.length; _i++) { 33795 var crit = _a[_i]; 33796 crit.criteria.setListeners(); 33797 } 33798 }); 33799 }; 33800 /** 33801 * Toggles the logic for the group 33802 */ 33803 Group.prototype._toggleLogic = function () { 33804 if (this.s.logic === 'OR') { 33805 this.s.logic = 'AND'; 33806 this.dom.logic.children().first().html(this.s.dt.i18n('searchBuilder.logicAnd', this.c.i18n.logicAnd)); 33807 } 33808 else if (this.s.logic === 'AND') { 33809 this.s.logic = 'OR'; 33810 this.dom.logic.children().first().html(this.s.dt.i18n('searchBuilder.logicOr', this.c.i18n.logicOr)); 33811 } 33812 }; 33813 Group.version = '1.1.0'; 33814 Group.classes = { 33815 add: 'dtsb-add', 33816 button: 'dtsb-button', 33817 clearGroup: 'dtsb-clearGroup', 33818 greyscale: 'dtsb-greyscale', 33819 group: 'dtsb-group', 33820 inputButton: 'dtsb-iptbtn', 33821 logic: 'dtsb-logic', 33822 logicContainer: 'dtsb-logicContainer' 33823 }; 33824 Group.defaults = { 33825 columns: true, 33826 conditions: { 33827 'date': Criteria.dateConditions, 33828 'html': Criteria.stringConditions, 33829 'html-num': Criteria.numConditions, 33830 'html-num-fmt': Criteria.numFmtConditions, 33831 'luxon': Criteria.luxonDateConditions, 33832 'moment': Criteria.momentDateConditions, 33833 'num': Criteria.numConditions, 33834 'num-fmt': Criteria.numFmtConditions, 33835 'string': Criteria.stringConditions 33836 }, 33837 depthLimit: false, 33838 enterSearch: false, 33839 filterChanged: undefined, 33840 greyscale: false, 33841 i18n: { 33842 add: 'Add Condition', 33843 button: { 33844 0: 'Search Builder', 33845 _: 'Search Builder (%d)' 33846 }, 33847 clearAll: 'Clear All', 33848 condition: 'Condition', 33849 data: 'Data', 33850 "delete": '×', 33851 deleteTitle: 'Delete filtering rule',
33852 left: '<', 33853 leftTitle: 'Outdent criteria', 33854 logicAnd: 'And', 33855 logicOr: 'Or', 33856 right: '>', 33857 rightTitle: 'Indent criteria', 33858 title: { 33859 0: 'Custom Search Builder', 33860 _: 'Custom Search Builder (%d)' 33861 }, 33862 value: 'Value', 33863 valueJoiner: 'and' 33864 }, 33865 logic: 'AND', 33866 orthogonal: { 33867 display: 'display', 33868 search: 'filter' 33869 }, 33870 preDefined: false 33871 }; 33872 return Group; 33873 }()); 33874 33875 var $$1; 33876 var dataTable$1; 33877 /** 33878 * Sets the value of jQuery for use in the file 33879 * 33880 * @param jq the instance of jQuery to be set 33881 */ 33882 function setJQuery(jq) { 33883 $$1 = jq; 33884 dataTable$1 = jq.fn.DataTable; 33885 } 33886 /** 33887 * SearchBuilder class for DataTables. 33888 * Allows for complex search queries to be constructed and implemented on a DataTable 33889 */ 33890 var SearchBuilder = /** @class */ (function () { 33891 function SearchBuilder(builderSettings, opts) { 33892 var _this = this; 33893 // Check that the required version of DataTables is included 33894 if (!dataTable$1 || !dataTable$1.versionCheck || !dataTable$1.versionCheck('1.10.0')) { 33895 throw new Error('SearchBuilder requires DataTables 1.10 or newer'); 33896 } 33897 var table = new dataTable$1.Api(builderSettings); 33898 this.classes = $$1.extend(true, {}, SearchBuilder.classes); 33899 // Get options from user 33900 this.c = $$1.extend(true, {}, SearchBuilder.defaults, opts); 33901 this.dom = { 33902 clearAll: $$1('<button type="button">' + table.i18n('searchBuilder.clearAll', this.c.i18n.clearAll) + '</button>') 33903 .addClass(this.classes.clearAll) 33904 .addClass(this.classes.button) 33905 .attr('type', 'button'), 33906 container: $$1('<div/>') 33907 .addClass(this.classes.container), 33908 title: $$1('<div/>') 33909 .addClass(this.classes.title), 33910 titleRow: $$1('<div/>') 33911 .addClass(this.classes.titleRow), 33912 topGroup: undefined 33913 }; 33914 this.s = { 33915 dt: table, 33916 opts: opts, 33917 search: undefined, 33918 serverData: undefined, 33919 topGroup: undefined 33920 }; 33921 // If searchbuilder is already defined for this table then return 33922 if (table.settings()[0]._searchBuilder !== undefined) { 33923 return; 33924 } 33925 table.settings()[0]._searchBuilder = this; 33926 // If using SSP we want to include the previous state in the very first server call 33927 if (this.s.dt.page.info().serverSide) { 33928 this.s.dt.on('preXhr.dtsb', function (e, settings, data) { 33929 var loadedState = _this.s.dt.state.loaded(); 33930 if (loadedState && loadedState.searchBuilder) { 33931 data.searchBuilder = _this._collapseArray(loadedState.searchBuilder); 33932 } 33933 }); 33934 this.s.dt.on('xhr.dtsb', function (e, settings, json) { 33935 if (json && json.searchBuilder && json.searchBuilder.options) { 33936 _this.s.serverData = json.searchBuilder.options; 33937 } 33938 }); 33939 } 33940 // Run the remaining setup when the table is initialised 33941 if (this.s.dt.settings()[0]._bInitComplete) { 33942 this._setUp(); 33943 } 33944 else { 33945 table.one('init.dt', function () { 33946 _this._setUp(); 33947 }); 33948 } 33949 return this; 33950 } 33951 /** 33952 * Gets the details required to rebuild the SearchBuilder as it currently is 33953 */ 33954 // eslint upset at empty object but that is what it is 33955 // eslint-disable-next-line @typescript-eslint/ban-types 33956 SearchBuilder.prototype.getDetails = function (deFormatDates) { 33957 if (deFormatDates === void 0) { deFormatDates = false; } 33958 return this.s.topGroup.getDetails(deFormatDates); 33959 }; 33960 /** 33961 * Getter for the node of the container for the searchBuilder 33962 * 33963 * @returns JQuery<HTMLElement> the node of the container 33964 */ 33965 SearchBuilder.prototype.getNode = function () { 33966 return this.dom.container; 33967 }; 33968 /** 33969 * Rebuilds the SearchBuilder to a state that is provided 33970 * 33971 * @param details The details required to perform a rebuild 33972 */ 33973 SearchBuilder.prototype.rebuild = function (details) { 33974 this.dom.clearAll.click(); 33975 // If there are no details to rebuild then return 33976 if (details === undefined || details === null) { 33977 return this; 33978 } 33979 this.s.topGroup.s.preventRedraw = true; 33980 this.s.topGroup.rebuild(details); 33981 this.s.topGroup.s.preventRedraw = false; 33982 this._checkClear(); 33983 this._updateTitle(this.s.topGroup.count()); 33984 this.s.topGroup.redrawContents(); 33985 this.s.dt.draw(false); 33986 this.s.topGroup.setListeners(); 33987 return this; 33988 }; 33989 /** 33990 * Applies the defaults to preDefined criteria 33991 * 33992 * @param preDef the array of criteria to be processed. 33993 */ 33994 SearchBuilder.prototype._applyPreDefDefaults = function (preDef) { 33995 var _this = this; 33996 if (preDef.criteria !== undefined && preDef.logic === undefined) { 33997 preDef.logic = 'AND'; 33998 } 33999 var _loop_1 = function (crit) { 34000 // Apply the defaults to any further criteria 34001 if (crit.criteria !== undefined) { 34002 crit = this_1._applyPreDefDefaults(crit); 34003 } 34004 else { 34005 this_1.s.dt.columns().every(function (index) { 34006 if (_this.s.dt.settings()[0].aoColumns[index].sTitle === crit.data) { 34007 crit.dataIdx = index; 34008 } 34009 }); 34010 } 34011 }; 34012 var this_1 = this; 34013 for (var _i = 0, _a = preDef.criteria; _i < _a.length; _i++) { 34014 var crit = _a[_i]; 34015 _loop_1(crit); 34016 } 34017 return preDef; 34018 }; 34019 /** 34020 * Set's up the SearchBuilder 34021 */ 34022 SearchBuilder.prototype._setUp = function (loadState) { 34023 var _this = this; 34024 if (loadState === void 0) { loadState = true; } 34025 // Register an Api method for getting the column type 34026 $$1.fn.DataTable.Api.registerPlural('columns().type()', 'column().type()', function () { 34027 return this.iterator('column', function (settings, column) { 34028 return settings.aoColumns[column].sType; 34029 }, 1); 34030 }); 34031 // Check that DateTime is included, If not need to check if it could be used 34032 // eslint-disable-next-line no-extra-parens 34033 if (!dataTable$1.DateTime) { 34034 var types = this.s.dt.columns().type().toArray(); 34035 if (types === undefined || types.includes(undefined) || types.includes(null)) { 34036 types = []; 34037 for (var _i = 0, _a = this.s.dt.settings()[0].aoColumns; _i < _a.length; _i++) { 34038 var colInit = _a[_i]; 34039 types.push(colInit.searchBuilderType !== undefined ? colInit.searchBuilderType : colInit.sType); 34040 } 34041 } 34042 var columnIdxs = this.s.dt.columns().toArray(); 34043 // If the types are not yet set then draw to see if they can be retrieved then 34044 if (types === undefined || types.includes(undefined) || types.includes(null)) { 34045 $$1.fn.dataTable.ext.oApi._fnColumnTypes(this.s.dt.settings()[0]); 34046 types = this.s.dt.columns().type().toArray(); 34047 } 34048 for (var i = 0; i < columnIdxs[0].length; i++) { 34049 var column = columnIdxs[0][i]; 34050 var type = types[column]; 34051 if ( 34052 // Check if this column can be filtered 34053 (this.c.columns === true || 34054 Array.isArray(this.c.columns) && 34055 this.c.columns.includes(i)) && 34056 // Check if the type is one of the restricted types 34057 (type.includes('date') || 34058 type.includes('moment') || 34059 type.includes('luxon'))) { 34060 alert('SearchBuilder Requires DateTime when used with dates.'); 34061 throw new Error('SearchBuilder requires DateTime'); 34062 } 34063 } 34064 } 34065 this.s.topGroup = new Group(this.s.dt, this.c, undefined, undefined, undefined, undefined, this.s.serverData); 34066 this._setClearListener(); 34067 this.s.dt.on('stateSaveParams.dtsb', function (e, settings, data) { 34068 data.searchBuilder = _this.getDetails(); 34069 if (!data.scroller) { 34070 data.page = _this.s.dt.page(); 34071 } 34072 else { 34073 data.start = _this.s.dt.state().start; 34074 } 34075 }); 34076 this.s.dt.on('stateLoadParams.dtsb', function (e, settings, data) { 34077 _this.rebuild(data.searchBuilder); 34078 }); 34079 this._build(); 34080 this.s.dt.on('preXhr.dtsb', function (e, settings, data) { 34081 if (_this.s.dt.page.info().serverSide) { 34082 data.searchBuilder = _this._collapseArray(_this.getDetails(true)); 34083 } 34084 }); 34085 this.s.dt.on('column-reorder', function () { 34086 _this.rebuild(_this.getDetails()); 34087 }); 34088 if (loadState) { 34089 var loadedState = this.s.dt.state.loaded(); 34090 // If the loaded State is not null rebuild based on it for statesave 34091 if (loadedState !== null && loadedState.searchBuilder !== undefined) { 34092 this.s.topGroup.rebuild(loadedState.searchBuilder); 34093 this.s.topGroup.dom.container.trigger('dtsb-redrawContents'); 34094 // If using SSP we want to restrict the amount of server calls that take place 34095 // and this information will already have been processed 34096 if (!this.s.dt.page.info().serverSide) { 34097 if (loadedState.page) { 34098 this.s.dt.page(loadedState.page).draw('page'); 34099 } 34100 else if (this.s.dt.scroller && loadedState.scroller) { 34101 this.s.dt.scroller().scrollToRow(loadedState.scroller.topRow); 34102 } 34103 } 34104 this.s.topGroup.setListeners(); 34105 } 34106 // Otherwise load any predefined options 34107 else if (this.c.preDefined !== false) { 34108 this.c.preDefined = this._applyPreDefDefaults(this.c.preDefined); 34109 this.rebuild(this.c.preDefined); 34110 } 34111 } 34112 this._setEmptyListener(); 34113 this.s.dt.state.save(); 34114 }; 34115 SearchBuilder.prototype._collapseArray = function (criteria) { 34116 if (criteria.logic === undefined) { 34117 if (criteria.value !== undefined) { 34118 criteria.value.sort(function (a, b) { 34119 if (!isNaN(+a)) { 34120 a = +a; 34121 b = +b; 34122 } 34123 if (a < b) { 34124 return -1; 34125 } 34126 else if (b < a) { 34127 return 1; 34128 } 34129 else { 34130 return 0; 34131 } 34132 }); 34133 criteria.value1 = criteria.value[0]; 34134 criteria.value2 = criteria.value[1]; 34135 } 34136 } 34137 else { 34138 for (var i = 0; i < criteria.criteria.length; i++) { 34139 criteria.criteria[i] = this._collapseArray(criteria.criteria[i]); 34140 } 34141 } 34142 return criteria; 34143 }; 34144 /** 34145 * Updates the title of the SearchBuilder 34146 * 34147 * @param count the number of filters in the SearchBuilder 34148 */ 34149 SearchBuilder.prototype._updateTitle = function (count) { 34150 this.dom.title.html(this.s.dt.i18n('searchBuilder.title', this.c.i18n.title, count)); 34151 }; 34152 /** 34153 * Builds all of the dom elements together 34154 */ 34155 SearchBuilder.prototype._build = function () { 34156 var _this = this; 34157 // Empty and setup the container 34158 this.dom.clearAll.remove(); 34159 this.dom.container.empty(); 34160 var count = this.s.topGroup.count(); 34161 this._updateTitle(count); 34162 this.dom.titleRow.append(this.dom.title); 34163 this.dom.container.append(this.dom.titleRow); 34164 this.dom.topGroup = this.s.topGroup.getNode(); 34165 this.dom.container.append(this.dom.topGroup); 34166 this._setRedrawListener(); 34167 var tableNode = this.s.dt.table(0).node(); 34168 if (!$$1.fn.dataTable.ext.search.includes(this.s.search)) { 34169 // Custom search function for SearchBuilder 34170 this.s.search = function (settings, searchData, dataIndex) { 34171 if (settings.nTable !== tableNode) { 34172 return true; 34173 } 34174 return _this.s.topGroup.search(searchData, dataIndex); 34175 }; 34176 // Add SearchBuilder search function to the dataTables search array 34177 $$1.fn.dataTable.ext.search.push(this.s.search); 34178 } 34179 this.s.dt.on('destroy.dtsb', function () { 34180 _this.dom.container.remove(); 34181 _this.dom.clearAll.remove(); 34182 var searchIdx = $$1.fn.dataTable.ext.search.indexOf(_this.s.search); 34183 while (searchIdx !== -1) { 34184 $$1.fn.dataTable.ext.search.splice(searchIdx, 1); 34185 searchIdx = $$1.fn.dataTable.ext.search.indexOf(_this.s.search); 34186 } 34187 _this.s.dt.off('.dtsb'); 34188 $$1(_this.s.dt.table().node()).off('.dtsb'); 34189 }); 34190 }; 34191 /** 34192 * Checks if the clearAll button should be added or not 34193 */ 34194 SearchBuilder.prototype._checkClear = function () { 34195 if (this.s.topGroup.s.criteria.length > 0) { 34196 this.dom.clearAll.insertAfter(this.dom.title); 34197 this._setClearListener(); 34198 } 34199 else { 34200 this.dom.clearAll.remove(); 34201 } 34202 }; 34203 /** 34204 * Update the count in the title/button 34205 * 34206 * @param count Number of filters applied 34207 */ 34208 SearchBuilder.prototype._filterChanged = function (count) { 34209 var fn = this.c.filterChanged; 34210 if (typeof fn === 'function') { 34211 fn(count, this.s.dt.i18n('searchBuilder.button', this.c.i18n.button, count)); 34212 } 34213 }; 34214 /** 34215 * Set the listener for the clear button 34216 */ 34217 SearchBuilder.prototype._setClearListener = function () { 34218 var _this = this; 34219 this.dom.clearAll.unbind('click'); 34220
34220 this.dom.clearAll.on('click.dtsb', function () { 34221 _this.s.topGroup = new Group(_this.s.dt, _this.c, undefined, undefined, undefined, undefined, _this.s.serverData); 34222 _this._build(); 34223 _this.s.dt.draw(); 34224 _this.s.topGroup.setListeners(); 34225 _this.dom.clearAll.remove(); 34226 _this._setEmptyListener(); 34227 _this._filterChanged(0); 34228 return false; 34229 }); 34230 }; 34231 /** 34232 * Set the listener for the Redraw event 34233 */ 34234 SearchBuilder.prototype._setRedrawListener = function () { 34235 var _this = this; 34236 this.s.topGroup.dom.container.unbind('dtsb-redrawContents'); 34237 this.s.topGroup.dom.container.on('dtsb-redrawContents.dtsb', function () { 34238 _this._checkClear(); 34239 _this.s.topGroup.redrawContents(); 34240 _this.s.topGroup.setupLogic(); 34241 _this._setEmptyListener(); 34242 var count = _this.s.topGroup.count(); 34243 _this._updateTitle(count); 34244 _this._filterChanged(count); 34245 // If using SSP we want to restrict the amount of server calls that take place 34246 // and this information will already have been processed 34247 if (!_this.s.dt.page.info().serverSide) { 34248 _this.s.dt.draw(); 34249 } 34250 _this.s.dt.state.save(); 34251 }); 34252 this.s.topGroup.dom.container.unbind('dtsb-redrawContents-noDraw'); 34253 this.s.topGroup.dom.container.on('dtsb-redrawContents-noDraw.dtsb', function () { 34254 _this._checkClear(); 34255 _this.s.topGroup.s.preventRedraw = true; 34256 _this.s.topGroup.redrawContents(); 34257 _this.s.topGroup.s.preventRedraw = false; 34258 _this.s.topGroup.setupLogic(); 34259 _this._setEmptyListener(); 34260 var count = _this.s.topGroup.count(); 34261 _this._updateTitle(count); 34262 _this._filterChanged(count); 34263 }); 34264 this.s.topGroup.dom.container.unbind('dtsb-redrawLogic'); 34265 this.s.topGroup.dom.container.on('dtsb-redrawLogic.dtsb', function () { 34266 _this.s.topGroup.redrawLogic(); 34267 var count = _this.s.topGroup.count(); 34268 _this._updateTitle(count); 34269 _this._filterChanged(count); 34270 }); 34271 this.s.topGroup.dom.container.unbind('dtsb-add'); 34272 this.s.topGroup.dom.container.on('dtsb-add.dtsb', function () { 34273 var count = _this.s.topGroup.count(); 34274 _this._updateTitle(count); 34275 _this._filterChanged(count); 34276 }); 34277 this.s.dt.on('postEdit.dtsb postCreate.dtsb postRemove.dtsb', function () { 34278 _this.s.topGroup.redrawContents(); 34279 }); 34280 this.s.topGroup.dom.container.unbind('dtsb-clearContents'); 34281 this.s.topGroup.dom.container.on('dtsb-clearContents.dtsb', function () { 34282 _this._setUp(false); 34283 _this._filterChanged(0); 34284 _this.s.dt.draw(); 34285 }); 34286 }; 34287 /** 34288 * Sets listeners to check whether clearAll should be added or removed 34289 */ 34290 SearchBuilder.prototype._setEmptyListener = function () { 34291 var _this = this; 34292 this.s.topGroup.dom.add.on('click.dtsb', function () { 34293 _this._checkClear(); 34294 }); 34295 this.s.topGroup.dom.container.on('dtsb-destroy.dtsb', function () { 34296 _this.dom.clearAll.remove(); 34297 }); 34298 }; 34299 SearchBuilder.version = '1.4.2'; 34300 SearchBuilder.classes = { 34301 button: 'dtsb-button', 34302 clearAll: 'dtsb-clearAll', 34303 container: 'dtsb-searchBuilder', 34304 inputButton: 'dtsb-iptbtn', 34305 title: 'dtsb-title', 34306 titleRow: 'dtsb-titleRow' 34307 }; 34308 SearchBuilder.defaults = { 34309 columns: true, 34310 conditions: { 34311 'date': Criteria.dateConditions, 34312 'html': Criteria.stringConditions, 34313 'html-num': Criteria.numConditions, 34314 'html-num-fmt': Criteria.numFmtConditions, 34315 'luxon': Criteria.luxonDateConditions, 34316 'moment': Criteria.momentDateConditions, 34317 'num': Criteria.numConditions, 34318 'num-fmt': Criteria.numFmtConditions, 34319 'string': Criteria.stringConditions 34320 }, 34321 depthLimit: false, 34322 enterSearch: false, 34323 filterChanged: undefined, 34324 greyscale: false, 34325 i18n: { 34326 add: 'Add Condition', 34327 button: { 34328 0: 'Search Builder', 34329 _: 'Search Builder (%d)' 34330 }, 34331 clearAll: 'Clear All', 34332 condition: 'Condition', 34333 conditions: { 34334 array: { 34335 contains: 'Contains', 34336 empty: 'Empty', 34337 equals: 'Equals', 34338 not: 'Not', 34339 notEmpty: 'Not Empty', 34340 without: 'Without' 34341 }, 34342 date: { 34343 after: 'After', 34344 before: 'Before', 34345 between: 'Between', 34346 empty: 'Empty', 34347 equals: 'Equals', 34348 not: 'Not', 34349 notBetween: 'Not Between', 34350 notEmpty: 'Not Empty' 34351 }, 34352 // eslint-disable-next-line id-blacklist 34353 number: { 34354 between: 'Between', 34355 empty: 'Empty', 34356 equals: 'Equals', 34357 gt: 'Greater Than', 34358 gte: 'Greater Than Equal To', 34359 lt: 'Less Than', 34360 lte: 'Less Than Equal To', 34361 not: 'Not', 34362 notBetween: 'Not Between', 34363 notEmpty: 'Not Empty' 34364 }, 34365 // eslint-disable-next-line id-blacklist
34366 string: { 34367 contains: 'Contains', 34368 empty: 'Empty', 34369 endsWith: 'Ends With', 34370 equals: 'Equals', 34371 not: 'Not', 34372 notContains: 'Does Not Contain', 34373 notEmpty: 'Not Empty', 34374 notEndsWith: 'Does Not End With', 34375 notStartsWith: 'Does Not Start With', 34376 startsWith: 'Starts With' 34377 } 34378 }, 34379 data: 'Data', 34380 "delete": '×', 34381 deleteTitle: 'Delete filtering rule', 34382 left: '<', 34383 leftTitle: 'Outdent criteria', 34384 logicAnd: 'And', 34385 logicOr: 'Or', 34386 right: '>', 34387 rightTitle: 'Indent criteria', 34388 title: { 34389 0: 'Custom Search Builder', 34390 _: 'Custom Search Builder (%d)' 34391 }, 34392 value: 'Value', 34393 valueJoiner: 'and' 34394 }, 34395 logic: 'AND', 34396 orthogonal: { 34397 display: 'display', 34398 search: 'filter' 34399 }, 34400 preDefined: false 34401 }; 34402 return SearchBuilder; 34403 }()); 34404 34405 /*! SearchBuilder 1.4.2 34406 * ©SpryMedia Ltd - datatables.net/license/mit 34407 */ 34408 setJQuery($); 34409 setJQuery$1($); 34410 setJQuery$2($); 34411 var dataTable = $.fn.dataTable; 34412 // eslint-disable-next-line no-extra-parens 34413 DataTable.SearchBuilder = SearchBuilder; 34414 // eslint-disable-next-line no-extra-parens 34415 dataTable.SearchBuilder = SearchBuilder; 34416 // eslint-disable-next-line no-extra-parens 34417 DataTable.Group = Group; 34418 // eslint-disable-next-line no-extra-parens 34419 dataTable.Group = Group; 34420 // eslint-disable-next-line no-extra-parens 34421 DataTable.Criteria = Criteria; 34422 // eslint-disable-next-line no-extra-parens 34423 dataTable.Criteria = Criteria; 34424 // eslint-disable-next-line no-extra-parens 34425 var apiRegister = DataTable.Api.register; 34426 // Set up object for plugins 34427 DataTable.ext.searchBuilder = { 34428 conditions: {} 34429 }; 34430 DataTable.ext.buttons.searchBuilder = { 34431 action: function (e, dt, node, config) { 34432 this.popover(config._searchBuilder.getNode(), { 34433 align: 'container', 34434 span: 'container' 34435 }); 34436 var topGroup = config._searchBuilder.s.topGroup; 34437 // Need to redraw the contents to calculate the correct positions for the elements 34438 if (topGroup !== undefined) { 34439 topGroup.dom.container.trigger('dtsb-redrawContents-noDraw'); 34440 } 34441 if (topGroup.s.criteria.length === 0) { 34442 $('.' + $.fn.dataTable.Group.classes.add.replace(/ /g, '.')).click(); 34443 } 34444 }, 34445 config: {}, 34446 init: function (dt, node, config) { 34447 var sb = new DataTable.SearchBuilder(dt, $.extend({ 34448 filterChanged: function (count, text) { 34449 dt.button(node).text(text); 34450 } 34451 }, config.config)); 34452 dt.button(node).text(config.text || dt.i18n('searchBuilder.button', sb.c.i18n.button, 0)); 34453 config._searchBuilder = sb; 34454 }, 34455 text: null 34456 }; 34457 apiRegister('searchBuilder.getDetails()', function (deFormatDates) { 34458 if (deFormatDates === void 0) { deFormatDates = false; } 34459 var ctx = this.context[0]; 34460 // If SearchBuilder has not been initialised on this instance then return 34461 return ctx._searchBuilder ? 34462 ctx._searchBuilder.getDetails(deFormatDates) : 34463 null; 34464 }); 34465 apiRegister('searchBuilder.rebuild()', function (details) { 34466 var ctx = this.context[0]; 34467 // If SearchBuilder has not been initialised on this instance then return 34468 if (ctx._searchBuilder === undefined) { 34469 return null; 34470 } 34471 ctx._searchBuilder.rebuild(details); 34472 return this; 34473 });
34474 apiRegister('searchBuilder.container()', function () { 34475 var ctx = this.context[0]; 34476 // If SearchBuilder has not been initialised on this instance then return 34477 return ctx._searchBuilder ? 34478 ctx._searchBuilder.getNode() : 34479 null; 34480 }); 34481 /** 34482 * Init function for SearchBuilder 34483 * 34484 * @param settings the settings to be applied 34485 * @param options the options for SearchBuilder 34486 * @returns JQUERY<HTMLElement> Returns the node of the SearchBuilder 34487 */ 34488 function _init(settings, options) { 34489 var api = new DataTable.Api(settings); 34490 var opts = options 34491 ? options 34492 : api.init().searchBuilder || DataTable.defaults.searchBuilder; 34493 var searchBuilder = new SearchBuilder(api, opts); 34494 var node = searchBuilder.getNode(); 34495 return node; 34496 } 34497 // Attach a listener to the document which listens for DataTables initialisation 34498 // events so we can automatically initialise 34499 $(document).on('preInit.dt.dtsp', function (e, settings) { 34500 if (e.namespace !== 'dt') { 34501 return; 34502 } 34503 if (settings.oInit.searchBuilder || 34504 DataTable.defaults.searchBuilder) { 34505 if (!settings._searchBuilder) { 34506 _init(settings); 34507 } 34508 } 34509 }); 34510 // DataTables `dom` feature option 34511 DataTable.ext.feature.push({ 34512 cFeature: 'Q', 34513 fnInit: _init 34514 }); 34515 // DataTables 2 layout feature 34516 if (DataTable.ext.features) { 34517 DataTable.ext.features.register('searchBuilder', _init); 34518 } 34519 34520})(); 34521 34522 34523return DataTable; 34524})); 34525 34526 34527/*! DataTables integration for DataTables' SearchBuilder 34528 * © SpryMedia Ltd - datatables.net/license 34529 */ 34530 34531(function( factory ){ 34532 if ( typeof define === 'function' && define.amd ) { 34533 // AMD 34534 define( ['jquery', 'datatables.net-dt', 'datatables.net-searchbuilder'], function ( $ ) { 34535 return factory( $, window, document ); 34536 } ); 34537 } 34538 else if ( typeof exports === 'object' ) { 34539 // CommonJS 34540 var jq = require('jquery'); 34541 var cjsRequires = function (root, $) { 34542 if ( ! $.fn.dataTable ) { 34543 require('datatables.net-dt')(root, $); 34544 } 34545 34546 if ( ! $.fn.dataTable.SearchBuilder ) { 34547 require('datatables.net-searchbuilder')(root, $); 34548 } 34549 }; 34550 34551 if (typeof window !== 'undefined') { 34552 module.exports = function (root, $) { 34553 if ( ! root ) { 34554 // CommonJS environments without a window global must pass a 34555 // root. This will give an error otherwise 34556 root = window; 34557 } 34558 34559 if ( ! $ ) { 34560 $ = jq( root ); 34561 } 34562 34563 cjsRequires( root, $ ); 34564 return factory( $, root, root.document ); 34565 }; 34566 } 34567 else { 34568 cjsRequires( window, jq ); 34569 module.exports = factory( jq, window, window.document ); 34570 } 34571 } 34572 else { 34573 // Browser 34574 factory( jQuery, window, document ); 34575 } 34576}(function( $, window, document, undefined ) { 34577'use strict'; 34578var DataTable = $.fn.dataTable; 34579 34580 34581 34582 34583return DataTable; 34584})); 34585 34586 34587/*! SearchPanes 2.1.2 34588 * © SpryMedia Ltd - datatables.net/license 34589 */ 34590 34591(function( factory ){ 34592 if ( typeof define === 'function' && define.amd ) { 34593 // AMD 34594 define( ['jquery', 'datatables.net'], function ( $ ) { 34595 return factory( $, window, document ); 34596 } ); 34597 } 34598 else if ( typeof exports === 'object' ) { 34599 // CommonJS 34600 var jq = require('jquery'); 34601 var cjsRequires = function (root, $) { 34602 if ( ! $.fn.dataTable ) { 34603 require('datatables.net')(root, $); 34604 } 34605 }; 34606 34607 if (typeof window !== 'undefined') { 34608 module.exports = function (root, $) { 34609 if ( ! root ) { 34610 // CommonJS environments without a window global must pass a 34611 // root. This will give an error otherwise 34612 root = window; 34613 } 34614 34615 if ( ! $ ) { 34616 $ = jq( root ); 34617 } 34618 34619 cjsRequires( root, $ ); 34620 return factory( $, root, root.document ); 34621 }; 34622 } 34623 else { 34624 cjsRequires( window, jq ); 34625 module.exports = factory( jq, window, window.document ); 34626 } 34627 } 34628 else { 34629 // Browser 34630 factory( jQuery, window, document ); 34631 } 34632}(function( $, window, document, undefined ) { 34633'use strict'; 34634var DataTable = $.fn.dataTable; 34635 34636 34637(function () { 34638 'use strict'; 34639 34640 var $$5; 34641 var dataTable$2; 34642 function setJQuery$4(jq) { 34643 $$5 = jq; 34644 dataTable$2 = jq.fn.dataTable; 34645 } 34646 var SearchPane = /** @class */ (function () { 34647 /** 34648 * Creates the panes, sets up the search function 34649 * 34650 * @param paneSettings The settings for the searchPanes 34651 * @param opts The options for the default features 34652 * @param index the index of the column for this pane 34653 * @param panesContainer The overall container for SearchPanes that this pane will be attached to 34654 * @param panes The custom pane settings if this is a custom pane 34655 * @returns {object} the pane that has been created, including the table and the index of the pane 34656 */ 34657 function SearchPane(paneSettings, opts, index, panesContainer, panes) { 34658 var _this = this; 34659 if (panes === void 0) { panes = null; }
34660 // Check that the required version of DataTables is included 34661 if (!dataTable$2 || !dataTable$2.versionCheck || !dataTable$2.versionCheck('1.10.0')) { 34662 throw new Error('SearchPane requires DataTables 1.10 or newer'); 34663 } 34664 // Check that Select is included 34665 // eslint-disable-next-line no-extra-parens 34666 if (!dataTable$2.select) { 34667 throw new Error('SearchPane requires Select'); 34668 } 34669 var table = new dataTable$2.Api(paneSettings); 34670 this.classes = $$5.extend(true, {}, SearchPane.classes); 34671 // Get options from user 34672 this.c = $$5.extend(true, {}, SearchPane.defaults, opts, panes); 34673 if (opts && opts.hideCount && opts.viewCount === undefined) { 34674 this.c.viewCount = !this.c.hideCount; 34675 } 34676 var rowLength = table.columns().eq(0).toArray().length; 34677 this.s = { 34678 colExists: index < rowLength, 34679 colOpts: undefined, 34680 customPaneSettings: panes, 34681 displayed: false, 34682 dt: table, 34683 dtPane: undefined, 34684 firstSet: true, 34685 index: index, 34686 indexes: [], 34687 listSet: false, 34688 name: undefined, 34689 rowData: { 34690 arrayFilter: [], 34691 arrayOriginal: [], 34692 bins: {}, 34693 binsOriginal: {}, 34694 filterMap: new Map(), 34695 totalOptions: 0 34696 }, 34697 scrollTop: 0, 34698 searchFunction: undefined, 34699 selections: [], 34700 serverSelect: [], 34701 serverSelecting: false, 34702 tableLength: null, 34703 updating: false 34704 }; 34705 this.s.colOpts = this.s.colExists ? this._getOptions() : this._getBonusOptions(); 34706 this.dom = { 34707 buttonGroup: $$5('<div/>').addClass(this.classes.buttonGroup), 34708 clear: $$5('<button type="button">×</button>') 34709 .attr('disabled', 'true') 34710 .addClass(this.classes.disabledButton) 34711 .addClass(this.classes.paneButton) 34712 .addClass(this.classes.clearButton) 34713 .html(this.s.dt.i18n('searchPanes.clearPane', this.c.i18n.clearPane)), 34714 collapseButton: $$5('<button type="button"><span class="' + this.classes.caret + '">^</span></button>') 34715 .addClass(this.classes.paneButton) 34716 .addClass(this.classes.collapseButton), 34717 container: $$5('<div/>') 34718 .addClass(this.classes.container) 34719 .addClass(this.s.colOpts.className) 34720 .addClass(this.classes.layout + 34721 (parseInt(this.c.layout.split('-')[1], 10) < 10 ? 34722 this.c.layout : 34723 this.c.layout.split('-')[0] + '-9')) 34724 .addClass(this.s.customPaneSettings && this.s.customPaneSettings.className 34725 ? this.s.customPaneSettings.className 34726 : ''), 34727 countButton: $$5('<button type="button"></button>') 34728 .addClass(this.classes.paneButton) 34729 .addClass(this.classes.countButton), 34730 dtP: $$5('<table><thead><tr><th>' + 34731 (this.s.colExists 34732 ? $$5(this.s.dt.column(this.s.index).header()).text() 34733 : this.s.customPaneSettings.header || 'Custom Pane') + '</th><th/></tr></thead></table>'), 34734 lower: $$5('<div/>').addClass(this.classes.subRow2).addClass(this.classes.narrowButton), 34735 nameButton: $$5('<button type="button"></button>') 34736 .addClass(this.classes.paneButton) 34737 .addClass(this.classes.nameButton), 34738 panesContainer: panesContainer, 34739 searchBox: $$5('<input/>').addClass(this.classes.paneInputButton).addClass(this.classes.search), 34740 searchButton: $$5('<button type = "button"/>') 34741 .addClass(this.classes.searchIcon) 34742 .addClass(this.classes.paneButton), 34743 searchCont: $$5('<div/>').addClass(this.classes.searchCont), 34744 searchLabelCont: $$5('<div/>').addClass(this.classes.searchLabelCont), 34745 topRow: $$5('<div/>').addClass(this.classes.topRow), 34746 upper: $$5('<div/>').addClass(this.classes.subRow1).addClass(this.classes.narrowSearch) 34747 }; 34748 // Set the value of name incase ordering is desired 34749 if (this.s.colOpts.name) { 34750 this.s.name = this.s.colOpts.name; 34751 } 34752 else if (this.s.customPaneSettings && this.s.customPaneSettings.name) { 34753 this.s.name = this.s.customPaneSettings.name; 34754 } 34755 else { 34756 this.s.name = this.s.colExists ? 34757 $$5(this.s.dt.column(this.s.index).header()).text() : 34758 this.s.customPaneSettings.header || 'Custom Pane'; 34759 } 34760 var tableNode = this.s.dt.table(0).node(); 34761 // Custom search function for table 34762 this.s.searchFunction = function (settings, searchData, dataIndex) { 34763 // If no data has been selected then show all 34764 if (_this.s.selections.length === 0) { 34765 return true; 34766 } 34767 if (settings.nTable !== tableNode) { 34768 return true; 34769 } 34770 var filter = null; 34771 if (_this.s.colExists) { 34772 // Get the current filtered data 34773 filter = searchData[_this.s.index]; 34774 if (_this.s.colOpts.orthogonal.filter !== 'filter') { 34775 // get the filter value from the map 34776 filter = _this.s.rowData.filterMap.get(dataIndex); 34777 if (filter instanceof $$5.fn.dataTable.Api) { 34778 // eslint-disable-next-line no-extra-parens
34779 filter = filter.toArray(); 34780 } 34781 } 34782 } 34783 return _this._search(filter, dataIndex); 34784 }; 34785 $$5.fn.dataTable.ext.search.push(this.s.searchFunction); 34786 // If the clear button for this pane is clicked clear the selections 34787 if (this.c.clear) { 34788 this.dom.clear.on('click.dtsp', function () { 34789 var searches = _this.dom.container.find('.' + _this.classes.search.replace(/\s+/g, '.')); 34790 searches.each(function () { 34791 $$5(this).val('').trigger('input'); 34792 }); 34793 _this.clearPane(); 34794 }); 34795 } 34796 // Sometimes the top row of the panes containing the search box and ordering buttons appears 34797 // weird if the width of the panes is lower than expected, this fixes the design. 34798 // Equally this may occur when the table is resized. 34799 this.s.dt.on('draw.dtsp', function () { return _this.adjustTopRow(); }); 34800 this.s.dt.on('buttons-action.dtsp', function () { return _this.adjustTopRow(); }); 34801 // When column-reorder is present and the columns are moved, it is necessary to 34802 // reassign all of the panes indexes to the new index of the column. 34803 this.s.dt.on('column-reorder.dtsp', function (e, settings, details) { 34804 _this.s.index = details.mapping[_this.s.index]; 34805 }); 34806 return this; 34807 } 34808 /** 34809 * Adds a row to the panes table 34810 * 34811 * @param display the value to be displayed to the user 34812 * @param filter the value to be filtered on when searchpanes is implemented 34813 * @param shown the number of rows in the table that are currently visible matching this criteria 34814 * @param total the total number of rows in the table that match this criteria 34815 * @param sort the value to be sorted in the pane table 34816 * @param type the value of which the type is to be derived from 34817 */ 34818 SearchPane.prototype.addRow = function (display, filter, sort, type, className, total, shown) { 34819 if (!total) { 34820 total = this.s.rowData.bins[filter] ? 34821 this.s.rowData.bins[filter] : 34822 0; 34823 } 34824 if (!shown) { 34825 shown = this._getShown(filter); 34826 } 34827 var index; 34828 for (var _i = 0, _a = this.s.indexes; _i < _a.length; _i++) { 34829 var entry = _a[_i]; 34830 if (entry.filter === filter) { 34831 index = entry.index; 34832 } 34833 } 34834 if (index === undefined) { 34835 index = this.s.indexes.length; 34836 this.s.indexes.push({ filter: filter, index: index }); 34837 } 34838 return this.s.dtPane.row.add({ 34839 className: className, 34840 display: display !== '' ? 34841 display : 34842 this.emptyMessage(), 34843 filter: filter, 34844 index: index, 34845 shown: shown, 34846 sort: sort, 34847 total: total, 34848 type: type 34849 }); 34850 }; 34851 /** 34852 * Adjusts the layout of the top row when the screen is resized 34853 */ 34854 SearchPane.prototype.adjustTopRow = function () { 34855 var subContainers = this.dom.container.find('.' + this.classes.subRowsContainer.replace(/\s+/g, '.')); 34856 var subRow1 = this.dom.container.find('.' + this.classes.subRow1.replace(/\s+/g, '.')); 34857 var subRow2 = this.dom.container.find('.' + this.classes.subRow2.replace(/\s+/g, '.')); 34858 var topRow = this.dom.container.find('.' + this.classes.topRow.replace(/\s+/g, '.')); 34859 // If the width is 0 then it is safe to assume that the pane has not yet been displayed. 34860 // Even if it has, if the width is 0 it won't make a difference if it has the narrow class or not 34861 if (($$5(subContainers[0]).width() < 252 || $$5(topRow[0]).width() < 252) && $$5(subContainers[0]).width() !== 0) { 34862 $$5(subContainers[0]).addClass(this.classes.narrow); 34863 $$5(subRow1[0]).addClass(this.classes.narrowSub).removeClass(this.classes.narrowSearch); 34864 $$5(subRow2[0]).addClass(this.classes.narrowSub).removeClass(this.classes.narrowButton); 34865 } 34866 else { 34867 $$5(subContainers[0]).removeClass(this.classes.narrow); 34868 $$5(subRow1[0]).removeClass(this.classes.narrowSub).addClass(this.classes.narrowSearch); 34869 $$5(subRow2[0]).removeClass(this.classes.narrowSub).addClass(this.classes.narrowButton); 34870 } 34871 }; 34872 /** 34873 * In the case of a rebuild there is potential for new data to have been included or removed 34874 * so all of the rowData must be reset as a precaution. 34875 */ 34876 SearchPane.prototype.clearData = function () { 34877 this.s.rowData = { 34878 arrayFilter: [], 34879 arrayOriginal: [], 34880 bins: {}, 34881 binsOriginal: {}, 34882 filterMap: new Map(), 34883 totalOptions: 0 34884 }; 34885 }; 34886 /** 34887 * Clear the selections in the pane 34888 */ 34889 SearchPane.prototype.clearPane = function () { 34890 // Deselect all rows which are selected and update the table and filter count. 34891 this.s.dtPane.rows({ selected: true }).deselect(); 34892 this.updateTable(); 34893 return this; 34894 }; 34895 /** 34896 * Collapses the pane so that only the header is displayed 34897 */ 34898 SearchPane.prototype.collapse = function () { 34899 var _this = this; 34900 if (!this.s.displayed || 34901 ( 34902 // If collapsing is disabled globally, and not enabled specifically for this column 34903 !this.c.collapse && this.s.colOpts.collapse !== true || 34904 // OR, collapsing could be enabled globally and this column specifically 34905 // is not to be collapsed. 34906 // We can't just take !this.s.colOpts.collapse here as if it is undefined 34907 // then the global should be used 34908 this.s.colOpts.collapse === false)) { 34909 return; 34910 } 34911 $$5(this.s.dtPane.table().container()).addClass(this.classes.hidden); 34912 this.dom.topRow.addClass(this.classes.bordered); 34913 this.dom.nameButton.addClass(this.classes.disabledButton); 34914 this.dom.countButton.addClass(this.classes.disabledButton); 34915 this.dom.searchButton.addClass(this.classes.disabledButton); 34916 this.dom.collapseButton.addClass(this.classes.rotated); 34917 this.dom.topRow.one('click.dtsp', function () { return _this.show(); }); 34918 this.dom.topRow.trigger('collapse.dtsps'); 34919 }; 34920 /** 34921 * Strips all of the SearchPanes elements from the document and turns all of the listeners for the buttons off
34922 */ 34923 SearchPane.prototype.destroy = function () { 34924 if (this.s.dtPane) { 34925 this.s.dtPane.off('.dtsp'); 34926 } 34927 this.s.dt.off('.dtsp'); 34928 this.dom.clear.off('.dtsp'); 34929 this.dom.nameButton.off('.dtsp'); 34930 this.dom.countButton.off('.dtsp'); 34931 this.dom.searchButton.off('.dtsp'); 34932 this.dom.collapseButton.off('.dtsp'); 34933 $$5(this.s.dt.table().node()).off('.dtsp'); 34934 this.dom.container.detach(); 34935 var searchIdx = $$5.fn.dataTable.ext.search.indexOf(this.s.searchFunction); 34936 while (searchIdx !== -1) { 34937 $$5.fn.dataTable.ext.search.splice(searchIdx, 1); 34938 searchIdx = $$5.fn.dataTable.ext.search.indexOf(this.s.searchFunction); 34939 } 34940 // If the datatables have been defined for the panes then also destroy these 34941 if (this.s.dtPane) { 34942 this.s.dtPane.destroy(); 34943 } 34944 this.s.listSet = false; 34945 }; 34946 /** 34947 * Getting the legacy message is a little complex due a legacy parameter 34948 */ 34949 SearchPane.prototype.emptyMessage = function () { 34950 var def = this.c.i18n.emptyMessage; 34951 // Legacy parameter support 34952 if (this.c.emptyMessage) { 34953 def = this.c.emptyMessage; 34954 } 34955 // Override per column 34956 if (this.s.colOpts.emptyMessage !== false && this.s.colOpts.emptyMessage !== null) { 34957 def = this.s.colOpts.emptyMessage; 34958 } 34959 return this.s.dt.i18n('searchPanes.emptyMessage', def); 34960 }; 34961 /** 34962 * Updates the number of filters that have been applied in the title 34963 */ 34964 SearchPane.prototype.getPaneCount = function () { 34965 return this.s.dtPane ? 34966 this.s.dtPane.rows({ selected: true }).data().toArray().length : 34967 0; 34968 }; 34969 /** 34970 * Rebuilds the panes from the start having deleted the old ones 34971 * 34972 * @param? dataIn data to be used in buildPane 34973 * @param? maintainSelection Whether the current selections are to be maintained over rebuild 34974 */ 34975 SearchPane.prototype.rebuildPane = function (dataIn, maintainSelection) { 34976 if (dataIn === void 0) { dataIn = null; } 34977 if (maintainSelection === void 0) { maintainSelection = false; } 34978 this.clearData(); 34979 var selectedRows = []; 34980 this.s.serverSelect = []; 34981 var prevEl = null; 34982 // When rebuilding strip all of the HTML Elements out of the container and start from scratch 34983 if (this.s.dtPane) { 34984 if (maintainSelection) { 34985 if (!this.s.dt.page.info().serverSide) { 34986 selectedRows = this.s.dtPane.rows({ selected: true }).data().toArray(); 34987 } 34988 else { 34989 this.s.serverSelect = this.s.dtPane.rows({ selected: true }).data().toArray(); 34990 } 34991 } 34992 this.s.dtPane.clear().destroy(); 34993 prevEl = this.dom.container.prev(); 34994 this.destroy(); 34995 this.s.dtPane = undefined; 34996 $$5.fn.dataTable.ext.search.push(this.s.searchFunction); 34997 } 34998 this.dom.container.removeClass(this.classes.hidden); 34999 this.s.displayed = false; 35000 this._buildPane(!this.s.dt.page.info().serverSide ? 35001 selectedRows : 35002 this.s.serverSelect, dataIn, prevEl); 35003 return this; 35004 }; 35005 /** 35006 * Resizes the pane based on the layout that is passed in 35007 * 35008 * @param layout the layout to be applied to this pane 35009 */ 35010 SearchPane.prototype.resize = function (layout) { 35011 this.c.layout = layout; 35012 this.dom.container 35013 .removeClass() 35014 .addClass(this.classes.show) 35015 .addClass(this.classes.container) 35016 .addClass(this.s.colOpts.className) 35017 .addClass(this.classes.layout + 35018 (parseInt(layout.split('-')[1], 10) < 10 ? 35019 layout : 35020 layout.split('-')[0] + '-9')) 35021 .addClass(this.s.customPaneSettings !== null && this.s.customPaneSettings.className 35022 ? this.s.customPaneSettings.className 35023 : ''); 35024 this.adjustTopRow(); 35025 }; 35026 /** 35027 * Sets the listeners for the pane. 35028 * 35029 * Having it in it's own function makes it easier to only set them once 35030 */ 35031 SearchPane.prototype.setListeners = function () { 35032 var _this = this; 35033 if (!this.s.dtPane) { 35034 return; 35035 } 35036 // When an item is selected on the pane, add these to the array which holds selected items. 35037 // Custom search will perform. 35038 this.s.dtPane.off('select.dtsp').on('select.dtsp', function () { 35039 clearTimeout(_this.s.deselectTimeout); 35040 _this._updateSelection(!_this.s.updating); 35041 _this.dom.clear.removeClass(_this.classes.disabledButton).removeAttr('disabled'); 35042 }); 35043 // When an item is deselected on the pane, re add the currently selected items to the array 35044 // which holds selected items. Custom search will be performed. 35045 this.s.dtPane.off('deselect.dtsp').on('deselect.dtsp', function () { 35046 _this.s.deselectTimeout = setTimeout(function () { 35047 _this._updateSelection(true); 35048 if (_this.s.dtPane.rows({ selected: true }).data().toArray().length === 0) { 35049 _this.dom.clear.addClass(_this.classes.disabledButton).attr('disabled', 'true'); 35050 } 35051 }, 50); 35052 }); 35053 // If we attempty to turn off this event then it will ruin behaviour in other panes 35054 // so need to make sure that it is only done once 35055 if (this.s.firstSet) { 35056 this.s.firstSet = false; 35057 // When saving the state store all of the selected rows for preselection next time around 35058 this.s.dt.on('stateSaveParams.dtsp', function (e, settings, data) { 35059 // If the data being passed in is empty then state clear must have occured 35060 // so clear the panes state as well 35061 if ($$5.isEmptyObject(data)) { 35062 _this.s.dtPane.state.clear(); 35063 return; 35064 } 35065 var bins; 35066 var order; 35067 var selected = []; 35068 var collapsed; 35069 var searchTerm; 35070 var arrayFilter; 35071 // Get all of the data needed for the state save from the pane 35072 if (_this.s.dtPane) { 35073 selected = _this.s.dtPane 35074 .rows({ selected: true }) 35075 .data() 35076 .map(function (item) { return item.filter.toString(); }) 35077 .toArray(); 35078 searchTerm = _this.dom.searchBox.val(); 35079 order = _this.s.dtPane.order(); 35080 bins = _this.s.rowData.binsOriginal; 35081 arrayFilter = _this.s.rowData.arrayOriginal; 35082 collapsed = _this.dom.collapseButton.hasClass(_this.classes.rotated); 35083 } 35084 if (data.searchPanes === undefined) { 35085 data.searchPanes = {}; 35086 } 35087 if (data.searchPanes.panes === undefined) { 35088 data.searchPanes.panes = []; 35089 } 35090 for (var i = 0; i < data.searchPanes.panes.length; i++) { 35091 if (data.searchPanes.panes[i].id === _this.s.index) { 35092 data.searchPanes.panes.splice(i, 1); 35093 i--; 35094 } 35095 } 35096 // Add the panes data to the state object 35097 data.searchPanes.panes.push({ 35098 arrayFilter: arrayFilter, 35099 bins: bins, 35100 collapsed: collapsed, 35101 id: _this.s.index, 35102 order: order, 35103 searchTerm: searchTerm, 35104 selected: selected 35105 }); 35106 }); 35107 } 35108 this.s.dtPane.off('user-select.dtsp').on('user-select.dtsp', function (e, _dt, type, cell, originalEvent) { 35109 originalEvent.stopPropagation(); 35110 }); 35111 this.s.dtPane.off('draw.dtsp').on('draw.dtsp', function () { return _this.adjustTopRow(); }); 35112 // When the button to order by the name of the options is clicked then 35113 // change the ordering to whatever it isn't currently 35114 this.dom.nameButton.off('click.dtsp').on('click.dtsp', function () { 35115 var currentOrder = _this.s.dtPane.order()[0][1]; 35116 _this.s.dtPane.order([0, currentOrder === 'asc' ? 'desc' : 'asc']).draw(); 35117 // This state save is required so that the ordering of the panes is maintained 35118 _this.s.dt.state.save(); 35119 }); 35120 // When the button to order by the number of entries in the column is clicked then 35121 // change the ordering to whatever it isn't currently 35122 this.dom.countButton.off('click.dtsp').on('click.dtsp', function () { 35123 var currentOrder = _this.s.dtPane.order()[0][1]; 35124 _this.s.dtPane.order([1, currentOrder === 'asc' ? 'desc' : 'asc']).draw(); 35125 // This state save is required so that the ordering of the panes is maintained 35126 _this.s.dt.state.save(); 35127 }); 35128 // When the button to order by the number of entries in the column is clicked then 35129 // change the ordering to whatever it isn't currently 35130 this.dom.collapseButton.off('click.dtsp').on('click.dtsp', function (e) { 35131 e.stopPropagation(); 35132 var container = $$5(_this.s.dtPane.table().container()); 35133 // Toggle the classes 35134 container.toggleClass(_this.classes.hidden); 35135 _this.dom.topRow.toggleClass(_this.classes.bordered); 35136 _this.dom.nameButton.toggleClass(_this.classes.disabledButton); 35137 _this.dom.countButton.toggleClass(_this.classes.disabledButton); 35138 _this.dom.searchButton.toggleClass(_this.classes.disabledButton); 35139 _this.dom.collapseButton.toggleClass(_this.classes.rotated); 35140 if (container.hasClass(_this.classes.hidden)) { 35141 _this.dom.topRow.on('click.dtsp', function () { return _this.dom.collapseButton.click(); }); 35142 } 35143 else { 35144 _this.dom.topRow.off('click.dtsp'); 35145 } 35146 _this.s.dt.state.save(); 35147 _this.dom.topRow.trigger('collapse.dtsps'); 35148 }); 35149 // When the clear button is clicked reset the pane 35150 this.dom.clear.off('click.dtsp').on('click.dtsp', function () { 35151 var searches = _this.dom.container.find('.' + _this.classes.search.replace(/ /g, '.')); 35152 searches.each(function () { 35153 // set the value of the search box to be an empty string and then search on that, effectively reseting 35154 $$5(this).val('').trigger('input'); 35155 }); 35156 _this.clearPane(); 35157 }); 35158 // When the search button is clicked then draw focus to the search box 35159 this.dom.searchButton.off('click.dtsp').on('click.dtsp', function () { return _this.dom.searchBox.focus(); }); 35160 // When a character is inputted into the searchbox search the pane for matching values. 35161 // Doing it this way means that no button has to be clicked to trigger a search, it is done asynchronously 35162 this.dom.searchBox.off('click.dtsp').on('input.dtsp', function () { 35163 var searchval = _this.dom.searchBox.val(); 35164 _this.s.dtPane.search(searchval).draw(); 35165 if (typeof searchval === 'string' && 35166 (searchval.length > 0 || 35167 searchval.length === 0 && _this.s.dtPane.rows({ selected: true }).data().toArray().length > 0)) { 35168 _this.dom.clear.removeClass(_this.classes.disabledButton).removeAttr('disabled'); 35169 } 35170 else { 35171 _this.dom.clear.addClass(_this.classes.disabledButton).attr('disabled', 'true'); 35172 } 35173 // This state save is required so that the searching on the panes is maintained 35174 _this.s.dt.state.save(); 35175 }); 35176 this.s.dtPane.select.style(this.s.colOpts.dtOpts && this.s.colOpts.dtOpts.select && this.s.colOpts.dtOpts.select.style 35177 ? this.s.colOpts.dtOpts.select.style 35178 : this.c.dtOpts && this.c.dtOpts.select && this.c.dtOpts.select.style 35179 ? this.c.dtOpts.select.style 35180 : 'os'); 35181 }; 35182 /** 35183 * Populates the SearchPane based off of the data that has been recieved from the server 35184 * 35185 * This method is overriden by SearchPaneST 35186 * 35187 * @param dataIn The data that has been sent from the server 35188 */ 35189 SearchPane.prototype._serverPopulate = function (dataIn) { 35190 if (dataIn.tableLength) { 35191 this.s.tableLength = dataIn.tableLength; 35192 this.s.rowData.totalOptions = this.s.tableLength; 35193 } 35194 else if (this.s.tableLength === null || this.s.dt.rows()[0].length > this.s.tableLength) { 35195 this.s.tableLength = this.s.dt.rows()[0].length; 35196 this.s.rowData.totalOptions = this.s.tableLength; 35197 } 35198 var colTitle = this.s.dt.column(this.s.index).dataSrc(); 35199 // If there is SP data for this column add it to the data array and bin 35200 if (dataIn.searchPanes.options[colTitle]) { 35201 for (var _i = 0, _a = dataIn.searchPanes.options[colTitle]; _i < _a.length; _i++) { 35202 var dataPoint = _a[_i]; 35203 this.s.rowData.arrayFilter.push({ 35204 display: dataPoint.label, 35205 filter: dataPoint.value, 35206 sort: dataPoint.label, 35207 type: dataPoint.label 35208 }); 35209 this.s.rowData.bins[dataPoint.value] = dataPoint.total; 35210 } 35211 } 35212 var binLength = Object.keys(this.s.rowData.bins).length; 35213 var uniqueRatio = this._uniqueRatio(binLength, this.s.tableLength); 35214 // Don't show the pane if there isnt enough variance in the data, or there is only 1 entry for that pane 35215 if (this.s.displayed === false && 35216 ((this.s.colOpts.show === undefined && this.s.colOpts.threshold === null ? 35217 uniqueRatio > this.c.threshold : 35218 uniqueRatio > this.s.colOpts.threshold) || 35219 this.s.colOpts.show !== true && binLength <= 1)) { 35220 this.dom.container.addClass(this.classes.hidden); 35221 this.s.displayed = false; 35222 return; 35223 } 35224 // Store the original data 35225 this.s.rowData.arrayOriginal = this.s.rowData.arrayFilter; 35226 this.s.rowData.binsOriginal = this.s.rowData.bins; 35227 // Flag this pane as being displayed 35228 this.s.displayed = true; 35229 }; 35230 /**
35231 * Expands the pane from the collapsed state 35232 */ 35233 SearchPane.prototype.show = function () { 35234 if (!this.s.displayed) { 35235 return; 35236 } 35237 this.dom.topRow.removeClass(this.classes.bordered); 35238 this.dom.nameButton.removeClass(this.classes.disabledButton); 35239 this.dom.countButton.removeClass(this.classes.disabledButton); 35240 this.dom.searchButton.removeClass(this.classes.disabledButton); 35241 this.dom.collapseButton.removeClass(this.classes.rotated); 35242 $$5(this.s.dtPane.table().container()).removeClass(this.classes.hidden); 35243 this.dom.topRow.trigger('collapse.dtsps'); 35244 }; 35245 /** 35246 * Finds the ratio of the number of different options in the table to the number of rows 35247 * 35248 * @param bins the number of different options in the table 35249 * @param rowCount the total number of rows in the table 35250 * @returns {number} returns the ratio 35251 */ 35252 SearchPane.prototype._uniqueRatio = function (bins, rowCount) { 35253 if (rowCount > 0 && 35254 (this.s.rowData.totalOptions > 0 && !this.s.dt.page.info().serverSide || 35255 this.s.dt.page.info().serverSide && this.s.tableLength > 0)) { 35256 return bins / this.s.rowData.totalOptions; 35257 } 35258 return 1; 35259 }; 35260 /** 35261 * Updates the panes if one of the options to do so has been set to true 35262 * rather than the filtered message when using viewTotal. 35263 */ 35264 SearchPane.prototype.updateTable = function () { 35265 var selectedRows = this.s.dtPane.rows({ selected: true }).data().toArray().map(function (el) { return el.filter; }); 35266 this.s.selections = selectedRows; 35267 this._searchExtras(); 35268 }; 35269 /** 35270 * Adds the custom options to the pane 35271 * 35272 * @returns {Array} Returns the array of rows which have been added to the pane 35273 */ 35274 SearchPane.prototype._getComparisonRows = function () { 35275 // Find the appropriate options depending on whether this is a pane for a specific column or a custom pane 35276 var options = this.s.colOpts.options 35277 ? this.s.colOpts.options 35278 : this.s.customPaneSettings && this.s.customPaneSettings.options 35279 ? this.s.customPaneSettings.options 35280 : undefined; 35281 if (options === undefined) { 35282 return; 35283 } 35284 var allRows = this.s.dt.rows(); 35285 var tableValsTotal = allRows.data().toArray(); 35286 var rows = []; 35287 // Clear all of the other rows from the pane, only custom options are to be displayed when they are defined 35288 this.s.dtPane.clear(); 35289 this.s.indexes = []; 35290 for (var _i = 0, options_1 = options; _i < options_1.length; _i++) { 35291 var comp = options_1[_i]; 35292 // Initialise the object which is to be placed in the row 35293 var insert = comp.label !== '' ? 35294 comp.label : 35295 this.emptyMessage(); 35296 var comparisonObj = { 35297 className: comp.className, 35298 display: insert, 35299 filter: typeof comp.value === 'function' ? comp.value : [], 35300 sort: insert, 35301 total: 0, 35302 type: insert 35303 }; 35304 // If a custom function is in place 35305 if (typeof comp.value === 'function') { 35306 // Count the number of times the function evaluates to true for the original data in the Table 35307 for (var i = 0; i < tableValsTotal.length; i++) { 35308 if (comp.value.call(this.s.dt, tableValsTotal[i], allRows[0][i])) { 35309 comparisonObj.total++; 35310 } 35311 } 35312 // Update the comparisonObj 35313 if (typeof comparisonObj.filter !== 'function') { 35314 comparisonObj.filter.push(comp.filter); 35315 } 35316 } 35317 rows.push(this.addRow(comparisonObj.display, comparisonObj.filter, comparisonObj.sort, comparisonObj.type, comparisonObj.className, comparisonObj.total)); 35318 } 35319 return rows; 35320 }; 35321 SearchPane.prototype._getMessage = function (row) { 35322 return this.s.dt.i18n('searchPanes.count', this.c.i18n.count).replace(/{total}/g, row.total); 35323 }; 35324 /** 35325 * Overridden in SearchPaneViewTotal and SearchPaneCascade to get the number of times a specific value is shown 35326 * 35327 * Here it is blanked so that it takes no action 35328 * 35329 * @param filter The filter value 35330 * @returns undefined 35331 */ 35332 // eslint-disable-next-line @typescript-eslint/no-unused-vars 35333 SearchPane.prototype._getShown = function (filter) { 35334 return undefined; 35335 }; 35336 /** 35337 * Get's the pane config appropriate to this class 35338 * 35339 * @returns The config needed to create a pane of this type 35340 */ 35341 SearchPane.prototype._getPaneConfig = function () { 35342 var _this = this; 35343 // eslint-disable-next-line no-extra-parens 35344 var haveScroller = dataTable$2.Scroller; 35345 var langOpts = this.s.dt.settings()[0].oLanguage; 35346 langOpts.url = undefined; 35347 langOpts.sUrl = undefined; 35348 return { 35349 columnDefs: [ 35350 { 35351 className: 'dtsp-nameColumn', 35352 data: 'display', 35353 render: function (data, type, row) { 35354 if (type === 'sort') { 35355 return row.sort; 35356 } 35357 else if (type === 'type') { 35358 return row.type; 35359 } 35360 var message = _this._getMessage(row); 35361 // We are displaying the count in the same columne as the name of the search option. 35362 // This is so that there is not need to call columns.adjust() 35363 // which in turn speeds up the code 35364 var pill = '<span class="' + _this.classes.pill + '">' + message + '</span>'; 35365 if (!_this.c.viewCount || !_this.s.colOpts.viewCount) { 35366 pill = ''; 35367 } 35368 if (type === 'filter') { 35369 return typeof data === 'string' && data.match(/<[^>]*>/) !== null ? 35370 data.replace(/<[^>]*>
35370/g, '') : 35371 data; 35372 } 35373 return '<div class="' + _this.classes.nameCont + '"><span title="' + 35374 (typeof data === 'string' && data.match(/<[^>]*>/) !== null ? 35375 data.replace(/<[^>]*>/g, '') : 35376 data) + 35377 '" class="' + _this.classes.name + '">' + 35378 data + '</span>' + 35379 pill + '</div>'; 35380 }, 35381 targets: 0, 35382 // Accessing the private datatables property to set type based on the original table. 35383 // This is null if not defined by the user, meaning that automatic type detection 35384 // would take place 35385 type: this.s.dt.settings()[0].aoColumns[this.s.index] ? 35386 this.s.dt.settings()[0].aoColumns[this.s.index]._sManualType : 35387 null 35388 }, 35389 { 35390 className: 'dtsp-countColumn ' + this.classes.badgePill, 35391 data: 'total', 35392 searchable: false, 35393 targets: 1, 35394 visible: false 35395 } 35396 ], 35397 deferRender: true, 35398 dom: 't', 35399 info: false, 35400 language: langOpts, 35401 paging: haveScroller ? true : false, 35402 scrollX: false, 35403 scrollY: '200px', 35404 scroller: haveScroller ? true : false, 35405 select: true, 35406 stateSave: this.s.dt.settings()[0].oFeatures.bStateSave ? true : false 35407 }; 35408 }; 35409 /** 35410 * This method allows for changes to the panes and table to be made when a selection or a deselection occurs 35411 */ 35412 SearchPane.prototype._makeSelection = function () { 35413 this.updateTable(); 35414 this.s.updating = true; 35415 this.s.dt.draw(false); 35416 this.s.updating = false; 35417 }; 35418 /** 35419 * Populates an array with all of the data for the table 35420 * 35421 * @param rowIdx The current row index to be compared 35422 * @param arrayFilter The array that is to be populated with row Details 35423 * @param settings The DataTable settings object 35424 * @param bins The bins object that is to be populated with the row counts 35425 */ 35426 SearchPane.prototype._populatePaneArray = function (rowIdx, arrayFilter, settings, bins) { 35427 if (bins === void 0) { bins = this.s.rowData.bins; } 35428 // Retrieve the rendered data from the cell using the fnGetCellData function 35429 // rather than the cell().render API method for optimisation 35430 if (typeof this.s.colOpts.orthogonal === 'string') { 35431 var rendered = settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, this.s.colOpts.orthogonal); 35432 this.s.rowData.filterMap.set(rowIdx, rendered); 35433 this._addOption(rendered, rendered, rendered, rendered, arrayFilter, bins); 35434 this.s.rowData.totalOptions++; 35435 } 35436 else { 35437 var filter = settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, this.s.colOpts.orthogonal.search); 35438 // Null and empty string are to be considered the same value 35439 if (filter === null) { 35440 filter = ''; 35441 } 35442 if (typeof filter === 'string') { 35443 filter = filter.replace(/<[^>]*>/g, ''); 35444 } 35445 this.s.rowData.filterMap.set(rowIdx, filter); 35446 if (!bins[filter]) { 35447 bins[filter] = 1; 35448 this._addOption(filter, settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, this.s.colOpts.orthogonal.display), settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, this.s.colOpts.orthogonal.sort), settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, this.s.colOpts.orthogonal.type), arrayFilter, bins); 35449 this.s.rowData.totalOptions++; 35450 } 35451 else { 35452 bins[filter]++; 35453 this.s.rowData.totalOptions++; 35454 } 35455 } 35456 }; 35457 /** 35458 * Reloads all of the previous selects into the panes 35459 * 35460 * @param loadedFilter The loaded filters from a previous state 35461 */ 35462 SearchPane.prototype._reloadSelect = function (loadedFilter) { 35463 // If the state was not saved don't selected any 35464 if (loadedFilter === undefined) { 35465 return; 35466 } 35467 var idx; 35468 // For each pane, check that the loadedFilter list exists and is not null, 35469 // find the id of each search item and set it to be selected. 35470 for (var i = 0; i < loadedFilter.searchPanes.panes.length; i++) { 35471 if (loadedFilter.searchPanes.panes[i].id === this.s.index) { 35472 idx = i; 35473 break; 35474 } 35475 } 35476 if (idx) { 35477 var table = this.s.dtPane; 35478 var rows = table.rows({ order: 'index' }).data().map(function (item) { return item.filter !== null ? 35479 item.filter.toString() : 35480 null; }).toArray(); 35481 for (var _i = 0, _a = loadedFilter.searchPanes.panes[idx].selected; _i < _a.length; _i++) { 35482 var filter = _a[_i]; 35483 var id = -1; 35484 if (filter !== null) { 35485 id = rows.indexOf(filter.toString()); 35486 } 35487 if (id > -1) { 35488 this.s.serverSelecting = true; 35489 table.row(id).select(); 35490 this.s.serverSelecting = false; 35491 } 35492 } 35493 } 35494 }; 35495 /** 35496 * Notes the rows that have been selected within this pane and stores them internally 35497 * 35498 * @param notUpdating Whether the panes are updating themselves or not 35499 */ 35500 SearchPane.prototype._updateSelection = function (notUpdating) { 35501 var _this = this; 35502 var settings = this.s.dt.settings()[0]; 35503 var oApi = settings.oApi; 35504 var run = function () { 35505 _this.s.scrollTop = $$5(_this.s.dtPane.table().node()).parent()[0].scrollTop; 35506 if (_this.s.dt.page.info().serverSide && !_this.s.updating) { 35507 if (!_this.s.serverSelecting) { 35508 _this.s.serverSelect = _this.s.dtPane.rows({ selected: true }).data().toArray(); 35509 _this.s.dt.draw(false); 35510 } 35511 } 35512 else if (notUpdating) { 35513 _this._makeSelection(); 35514 } 35515 oApi._fnProcessingDisplay(settings, false); 35516 }; 35517 // If the processing display is enabled, we need to allow the browser 35518 // to draw it before performing our calculations 35519 if (settings.oFeatures.bProcessing) { 35520 oApi._fnProcessingDisplay(settings, true); 35521 setTimeout(run, 1); 35522 } 35523 else { 35524 run(); 35525 } 35526 }; 35527 /** 35528 * Takes in potentially undetected rows and adds them to the array if they are not yet featured 35529 * 35530 * @param filter the filter value of the potential row 35531 * @param display the display value of the potential row 35532 * @param sort the sort value of the potential row 35533 * @param type the type value of the potential row 35534 * @param arrayFilter the array to be populated 35535 * @param bins the bins to be populated 35536 */ 35537 SearchPane.prototype._addOption = function (filter, display, sort, type, arrayFilter, bins) { 35538 // If the filter is an array then take a note of this, and add the elements to the arrayFilter array 35539 if (Array.isArray(filter) || filter instanceof dataTable$2.Api) { 35540 // Convert to an array so that we can work with it 35541 if (filter instanceof dataTable$2.Api) {
35542 filter = filter.toArray(); 35543 display = display.toArray(); 35544 } 35545 if (filter.length === display.length) { 35546 for (var i = 0; i < filter.length; i++) { 35547 // If we haven't seen this row before add it 35548 if (!bins[filter[i]]) { 35549 bins[filter[i]] = 1; 35550 arrayFilter.push({ 35551 display: display[i], 35552 filter: filter[i], 35553 sort: sort[i], 35554 type: type[i] 35555 }); 35556 } 35557 // Otherwise just increment the count 35558 else { 35559 bins[filter[i]]++; 35560 } 35561 this.s.rowData.totalOptions++; 35562 } 35563 return; 35564 } 35565 throw new Error('display and filter not the same length'); 35566 } 35567 // If the values were affected by othogonal data and are not an array then check if it is already present 35568 else if (typeof this.s.colOpts.orthogonal === 'string') { 35569 if (!bins[filter]) { 35570 bins[filter] = 1; 35571 arrayFilter.push({ 35572 display: display, 35573 filter: filter, 35574 sort: sort, 35575 type: type 35576 }); 35577 this.s.rowData.totalOptions++; 35578 } 35579 else { 35580 bins[filter]++; 35581 this.s.rowData.totalOptions++; 35582 } 35583 } 35584 // Otherwise we must just be adding an option 35585 else { 35586 arrayFilter.push({ 35587 display: display, 35588 filter: filter, 35589 sort: sort, 35590 type: type 35591 }); 35592 } 35593 }; 35594 /** 35595 * Method to construct the actual pane. 35596 * 35597 * @param selectedRows previously selected Rows to be reselected 35598 * @param dataIn Data that should be used to populate this pane 35599 * @param prevEl Reference to the previous element, used to ensure insert is in the correct location 35600 * @returns boolean to indicate whether this pane was the last one to have a selection made 35601 */ 35602 SearchPane.prototype._buildPane = function (selectedRows, dataIn, prevEl) { 35603 var _this = this; 35604 if (selectedRows === void 0) { selectedRows = []; } 35605 if (dataIn === void 0) { dataIn = null; } 35606 if (prevEl === void 0) { prevEl = null; } 35607 // Aliases 35608 this.s.selections = []; 35609 // Other Variables 35610 var loadedFilter = this.s.dt.state.loaded(); 35611 // If the listeners have not been set yet then using the latest state may result in funny errors 35612 if (this.s.listSet) { 35613 loadedFilter = this.s.dt.state(); 35614 } 35615 // If it is not a custom pane in place 35616 if (this.s.colExists) { 35617 var idx = -1; 35618 if (loadedFilter && loadedFilter.searchPanes && loadedFilter.searchPanes.panes) { 35619 for (var i = 0; i < loadedFilter.searchPanes.panes.length; i++) { 35620 if (loadedFilter.searchPanes.panes[i].id === this.s.index) { 35621 idx = i; 35622 break; 35623 } 35624 } 35625 } 35626 // Perform checks that do not require populate pane to run 35627 if ((this.s.colOpts.show === false || 35628 this.s.colOpts.show !== undefined && this.s.colOpts.show !== true) && 35629 idx === -1) { 35630 this.dom.container.addClass(this.classes.hidden); 35631 this.s.displayed = false; 35632 return false; 35633 } 35634 else if (this.s.colOpts.show === true || idx !== -1) { 35635 this.s.displayed = true; 35636 } 35637 if (!this.s.dt.page.info().serverSide && 35638 (!dataIn || 35639 !dataIn.searchPanes || 35640 !dataIn.searchPanes.options)) { 35641 // Only run populatePane if the data has not been collected yet 35642 if (this.s.rowData.arrayFilter.length === 0) { 35643 this.s.rowData.totalOptions = 0; 35644 this._populatePane(); 35645 this.s.rowData.arrayOriginal = this.s.rowData.arrayFilter; 35646 this.s.rowData.binsOriginal = this.s.rowData.bins; 35647 } 35648 var binLength = Object.keys(this.s.rowData.binsOriginal).length; 35649 var uniqueRatio = this._uniqueRatio(binLength, this.s.dt.rows()[0].length); 35650 // Don't show the pane if there isn't enough variance in the data, or there is only 1 entry 35651 // for that pane 35652 if (this.s.displayed === false && 35653 ((this.s.colOpts.show === undefined && this.s.colOpts.threshold === null ? 35654 uniqueRatio > this.c.threshold : 35655 uniqueRatio > this.s.colOpts.threshold) || 35656 this.s.colOpts.show !== true && binLength <= 1)) { 35657 this.dom.container.addClass(this.classes.hidden); 35658 this.s.displayed = false; 35659 return; 35660 } 35661 this.dom.container.addClass(this.classes.show); 35662 this.s.displayed = true; 35663 } 35664 else if (dataIn && dataIn.searchPanes && dataIn.searchPanes.options) { 35665 this._serverPopulate(dataIn); 35666 } 35667 } 35668 else { 35669 this.s.displayed = true; 35670 }
35671 // If the variance is accceptable then display the search pane 35672 this._displayPane(); 35673 if (!this.s.listSet) { 35674 // Here, when the state is loaded if the data object on the original table is empty, 35675 // then a state.clear() must have occurred, so delete all of the panes tables state objects too. 35676 this.dom.dtP.on('stateLoadParams.dtsp', function (e, settings, data) { 35677 if ($$5.isEmptyObject(_this.s.dt.state.loaded())) { 35678 $$5.each(data, function (index) { 35679 delete data[index]; 35680 }); 35681 } 35682 }); 35683 } 35684 // Add the container to the document in its original location 35685 if (prevEl !== null && this.dom.panesContainer.has(prevEl).length > 0) { 35686 this.dom.container.insertAfter(prevEl); 35687 } 35688 else { 35689 this.dom.panesContainer.prepend(this.dom.container); 35690 } 35691 // Declare the datatable for the pane 35692 var errMode = $$5.fn.dataTable.ext.errMode; 35693 $$5.fn.dataTable.ext.errMode = 'none'; 35694 // eslint-disable-next-line no-extra-parens 35695 this.s.dtPane = this.dom.dtP.DataTable($$5.extend(true, this._getPaneConfig(), this.c.dtOpts, this.s.colOpts ? this.s.colOpts.dtOpts : {}, this.s.colOpts.options || !this.s.colExists ? 35696 { 35697 createdRow: function (row, data) { 35698 $$5(row).addClass(data.className); 35699 } 35700 } : 35701 undefined, this.s.customPaneSettings !== null && this.s.customPaneSettings.dtOpts ? 35702 this.s.customPaneSettings.dtOpts : 35703 {}, $$5.fn.dataTable.versionCheck('2') 35704 ? { 35705 layout: { 35706 bottomLeft: null, 35707 bottomRight: null, 35708 topLeft: null, 35709 topRight: null 35710 } 35711 } 35712 : {})); 35713 this.dom.dtP.addClass(this.classes.table); 35714 // Getting column titles is a little messy 35715 var headerText = 'Custom Pane'; 35716 if (this.s.customPaneSettings && this.s.customPaneSettings.header) { 35717 headerText = this.s.customPaneSettings.header; 35718 } 35719 else if (this.s.colOpts.header) { 35720 headerText = this.s.colOpts.header; 35721 } 35722 else if (this.s.colExists) { 35723 headerText = $$5.fn.dataTable.versionCheck('2') 35724 ? this.s.dt.column(this.s.index).title() 35725 : this.s.dt.settings()[0].aoColumns[this.s.index].sTitle; 35726 } 35727 headerText = this._escapeHTML(headerText); 35728 this.dom.searchBox.attr('placeholder', headerText); 35729 // As the pane table is not in the document yet we must initialise select ourselves 35730 // eslint-disable-next-line no-extra-parens 35731 $$5.fn.dataTable.select.init(this.s.dtPane); 35732 $$5.fn.dataTable.ext.errMode = errMode; 35733 // If it is not a custom pane 35734 if (this.s.colExists) { 35735 // Add all of the search options to the pane 35736 for (var i = 0, ien = this.s.rowData.arrayFilter.length; i < ien; i++) { 35737 if (this.s.dt.page.info().serverSide) { 35738 var row = this.addRow(this.s.rowData.arrayFilter[i].display, this.s.rowData.arrayFilter[i].filter, this.s.rowData.arrayFilter[i].sort, this.s.rowData.arrayFilter[i].type); 35739 for (var _i = 0, _a = this.s.serverSelect; _i < _a.length; _i++) { 35740 var option = _a[_i]; 35741 if (option.filter === this.s.rowData.arrayFilter[i].filter) { 35742 this.s.serverSelecting = true; 35743 row.select(); 35744 this.s.serverSelecting = false; 35745 } 35746 } 35747 } 35748 else if (!this.s.dt.page.info().serverSide && this.s.rowData.arrayFilter[i]) { 35749 this.addRow(this.s.rowData.arrayFilter[i].display, this.s.rowData.arrayFilter[i].filter, this.s.rowData.arrayFilter[i].sort, this.s.rowData.arrayFilter[i].type); 35750 } 35751 else if (!this.s.dt.page.info().serverSide) { 35752 // Just pass an empty string as the message will be calculated based on that in addRow() 35753 this.addRow('', '', '', ''); 35754 } 35755 } 35756 } 35757 // eslint-disable-next-line no-extra-parens 35758 dataTable$2.select.init(this.s.dtPane); 35759 // If there are custom options set or it is a custom pane then get them 35760 if (this.s.colOpts.options || 35761 this.s.customPaneSettings && this.s.customPaneSettings.options) { 35762 this._getComparisonRows(); 35763 } 35764 // Display the pane 35765 this.s.dtPane.draw(); 35766 this.s.dtPane.table().node().parentNode.scrollTop = this.s.scrollTop; 35767 this.adjustTopRow(); 35768 this.setListeners(); 35769 this.s.listSet = true; 35770 for (var _b = 0, selectedRows_1 = selectedRows; _b < selectedRows_1.length; _b++) { 35771 var selection = selectedRows_1[_b]; 35772 if (selection) { 35773 for (var _c = 0, _d = this.s.dtPane.rows().indexes().toArray(); _c < _d.length; _c++) { 35774 var row = _d[_c]; 35775 if (this.s.dtPane.row(row).data() && 35776 selection.filter === this.s.dtPane.row(row).data().filter) { 35777 // If this is happening when serverSide processing is happening then 35778 // different behaviour is needed 35779 if (this.s.dt.page.info().serverSide) { 35780 this.s.serverSelecting = true; 35781 this.s.dtPane.row(row).select(); 35782 this.s.serverSelecting = false; 35783 } 35784 else { 35785 this.s.dtPane.row(row).select(); 35786 } 35787 } 35788 } 35789 } 35790 } 35791 // If SSP and the table is ready, apply the search for the pane 35792 if (this.s.dt.page.info().serverSide) { 35793 this.s.dtPane.search(this.dom.searchBox.val()).draw(); 35794 } 35795 if ((this.c.initCollapsed && this.s.colOpts.initCollapsed !== false || 35796 this.s.colOpts.initCollapsed) && 35797 (this.c.collapse && this.s.colOpts.collapse !== false || 35798 this.s.colOpts.collapse)) { 35799 // If the pane has not initialised yet then we need to wait for it to do so before collapsing 35800 // Otherwise the container that the class is added to does not exist 35801 if (this.s.dtPane.settings()[0]._bInitComplete) { 35802 this.collapse(); 35803 } 35804 else { 35805 this.s.dtPane.one('init', function () { return _this.collapse(); }); 35806 } 35807 } 35808 // Reload the selection, searchbox entry and ordering from the previous state 35809 // Need to check here if SSP that this is the first draw, otherwise it will infinite loop 35810 if (loadedFilter && 35811 loadedFilter.searchPanes && 35812 loadedFilter.searchPanes.panes && 35813 (!dataIn || 35814 dataIn.draw === 1)) { 35815 this._reloadSelect(loadedFilter); 35816 for (var _e = 0, _f = loadedFilter.searchPanes.panes; _e < _f.length; _e++) { 35817 var pane = _f[_e]; 35818 if (pane.id === this.s.index) { 35819 // Save some time by only triggering an input if there is a value 35820 if (pane.searchTerm && pane.searchTerm.length > 0) { 35821 this.dom.searchBox.val(pane.searchTerm).trigger('input'); 35822 } 35823 if (pane.order) { 35824 this.s.dtPane.order(pane.order).draw(); 35825 } 35826 // Is the pane to be hidden or shown? 35827 if (pane.collapsed) { 35828 this.collapse(); 35829 } 35830 else { 35831 this.show(); 35832 } 35833 } 35834 } 35835 } 35836 return true; 35837 }; 35838 /** 35839 * Appends all of the HTML elements to their relevant parent Elements 35840 */ 35841 SearchPane.prototype._displayPane = function () { 35842 // Empty everything to start again 35843 this.dom.dtP.empty(); 35844 this.dom.topRow.empty().addClass(this.classes.topRow); 35845 // If there are more than 3 columns defined then make there be a smaller gap between the panes 35846 if (parseInt(this.c.layout.split('-')[1], 10) > 3) { 35847 this.dom.container.addClass(this.classes.smallGap); 35848 } 35849 this.dom.topRow 35850 .addClass(this.classes.subRowsContainer) 35851 .append(this.dom.upper.append(this.dom.searchCont)) 35852 .append(this.dom.lower.append(this.dom.buttonGroup)); 35853 // If no selections have been made in the pane then disable the clear button 35854 if (this.c.dtOpts.searching === false || 35855 this.s.colOpts.dtOpts && this.s.colOpts.dtOpts.searching === false || 35856 (!this.c.controls || !this.s.colOpts.controls) || 35857 this.s.customPaneSettings && 35858 this.s.customPaneSettings.dtOpts && 35859 this.s.customPaneSettings.dtOpts.searching !== undefined && 35860 !this.s.customPaneSettings.dtOpts.searching) { 35861 this.dom.searchBox 35862 .removeClass(this.classes.paneInputButton) 35863 .addClass(this.classes.disabledButton) 35864 .attr('disabled', 'true'); 35865 } 35866 this.dom.searchBox.appendTo(this.dom.searchCont); 35867 // Create the contents of the searchCont div. Worth noting that this function will change when using semantic ui 35868 this._searchContSetup();
35869 // If the clear button is allowed to show then display it 35870 if (this.c.clear && this.c.controls && this.s.colOpts.controls) { 35871 this.dom.clear.appendTo(this.dom.buttonGroup); 35872 } 35873 if (this.c.orderable && this.s.colOpts.orderable && this.c.controls && this.s.colOpts.controls) { 35874 this.dom.nameButton.appendTo(this.dom.buttonGroup); 35875 } 35876 // If the count column is hidden then don't display the ordering button for it 35877 if (this.c.viewCount && 35878 this.s.colOpts.viewCount && 35879 this.c.orderable && 35880 this.s.colOpts.orderable && 35881 this.c.controls && 35882 this.s.colOpts.controls) { 35883 this.dom.countButton.appendTo(this.dom.buttonGroup); 35884 } 35885 if ((this.c.collapse && this.s.colOpts.collapse !== false || 35886 this.s.colOpts.collapse) && 35887 this.c.controls && this.s.colOpts.controls) { 35888 this.dom.collapseButton.appendTo(this.dom.buttonGroup); 35889 } 35890 this.dom.container.prepend(this.dom.topRow).append(this.dom.dtP).show(); 35891 }; 35892 /** 35893 * Escape html characters within a string 35894 * 35895 * @param txt the string to be escaped 35896 * @returns the escaped string 35897 */ 35898 SearchPane.prototype._escapeHTML = function (txt) { 35899 return txt 35900 .toString() 35901 .replace(/&/g, '&') 35902 .replace(/</g, '<') 35903 .replace(/>/g, '>') 35904 .replace(/"/g, '"'); 35905 }; 35906 /** 35907 * Gets the options for the row for the customPanes 35908 * 35909 * @returns {object} The options for the row extended to include the options from the user. 35910 */ 35911 SearchPane.prototype._getBonusOptions = function () { 35912 // We need to reset the thresholds as if they have a value in colOpts then that value will be used 35913 var defaultMutator = { 35914 threshold: null 35915 }; 35916 return $$5.extend(true, {}, SearchPane.defaults, defaultMutator, this.c ? this.c : {}); 35917 }; 35918 /** 35919 * Gets the options for the row for the customPanes 35920 * 35921 * @returns {object} The options for the row extended to include the options from the user. 35922 */ 35923 SearchPane.prototype._getOptions = function () { 35924 var table = this.s.dt; 35925 // We need to reset the thresholds as if they have a value in colOpts then that value will be used 35926 var defaultMutator = { 35927 collapse: null, 35928 emptyMessage: false, 35929 initCollapsed: null, 35930 threshold: null 35931 }; 35932 var columnOptions = table.settings()[0].aoColumns[this.s.index].searchPanes; 35933 var colOpts = $$5.extend(true, {}, SearchPane.defaults, defaultMutator, columnOptions); 35934 if (columnOptions && columnOptions.hideCount && columnOptions.viewCount === undefined) { 35935 colOpts.viewCount = !columnOptions.hideCount; 35936 } 35937 return colOpts; 35938 }; 35939 /** 35940 * Fill the array with the values that are currently being displayed in the table 35941 */ 35942 SearchPane.prototype._populatePane = function () { 35943 this.s.rowData.arrayFilter = []; 35944 this.s.rowData.bins = {}; 35945 var settings = this.s.dt.settings()[0]; 35946 if (!this.s.dt.page.info().serverSide) { 35947 for (var _i = 0, _a = this.s.dt.rows().indexes().toArray(); _i < _a.length; _i++) { 35948 var index = _a[_i]; 35949 this._populatePaneArray(index, this.s.rowData.arrayFilter, settings); 35950 } 35951 } 35952 }; 35953 /** 35954 * This method decides whether a row should contribute to the pane or not 35955 * 35956 * @param filter the value that the row is to be filtered on 35957 * @param dataIndex the row index 35958 */ 35959 SearchPane.prototype._search = function (filter, dataIndex) { 35960 var colOpts = this.s.colOpts; 35961 var table = this.s.dt; 35962 // For each item selected in the pane, check if it is available in the cell 35963 for (var _i = 0, _a = this.s.selections; _i < _a.length; _i++) { 35964 var colSelect = _a[_i]; 35965 if (typeof colSelect === 'string' && typeof filter === 'string') { 35966 // The filter value will not have the & in place but a &, 35967 // so we need to do a replace to make sure that they will match 35968 colSelect = this._escapeHTML(colSelect); 35969 } 35970 // if the filter is an array then is the column present in it 35971 if (Array.isArray(filter)) { 35972 if (colOpts.combiner === 'and') { 35973 if (!filter.includes(colSelect)) { 35974 return false; 35975 } 35976 } 35977 else if (filter.includes(colSelect)) { 35978 return true; 35979 } 35980 } 35981 // if the filter is a function then does it meet the criteria of that function or not 35982 else if (typeof colSelect === 'function') { 35983 if (colSelect.call(table, table.row(dataIndex).data(), dataIndex)) { 35984 if (colOpts.combiner === 'or') { 35985 return true; 35986 } 35987 } 35988 // If the combiner is an "and" then we need to check against all possible selections 35989 // so if it fails here then the and is not met and return false 35990 else if (colOpts.combiner === 'and') { 35991 return false; 35992 } 35993 } 35994 // otherwise if the two filter values are equal then return true 35995 else if (filter === colSelect || 35996 // Loose type checking incase number type in column comparing to a string 35997 // eslint-disable-next-line eqeqeq 35998 !(typeof filter === 'string' && filter.length === 0) && filter == colSelect || 35999 colSelect === null && typeof filter === 'string' && filter === '') { 36000 return true; 36001 } 36002 } 36003 // If the combiner is an and then we need to check against all possible selections 36004 // so return true here if so because it would have returned false earlier if it had failed 36005 if (colOpts.combiner === 'and') { 36006 return true; 36007 } 36008 // Otherwise it hasn't matched with anything by this point so it must be false 36009 return false; 36010 }; 36011 /** 36012 * Creates the contents of the searchCont div 36013 * 36014 * NOTE This is overridden when semantic ui styling in order to integrate the search button into the text box. 36015 */ 36016 SearchPane.prototype._searchContSetup = function () { 36017 if (this.c.controls && this.s.colOpts.controls) { 36018 this.dom.searchButton.appendTo(this.dom.searchLabelCont); 36019 } 36020 if (!(this.c.dtOpts.searching === false || 36021 this.s.colOpts.dtOpts.searching === false || 36022 this.s.customPaneSettings && 36023 this.s.customPaneSettings.dtOpts && 36024 this.s.customPaneSettings.dtOpts.searching !== undefined && 36025 !this.s.customPaneSettings.dtOpts.searching)) { 36026 this.dom.searchLabelCont.appendTo(this.dom.searchCont); 36027 } 36028 }; 36029 /** 36030 * Adds outline to the pane when a selection has been made 36031 */ 36032 SearchPane.prototype._searchExtras = function () { 36033 var updating = this.s.updating; 36034 this.s.updating = true; 36035 var filters = this.s.dtPane.rows({ selected: true }
36035).data().pluck('filter').toArray(); 36036 var nullIndex = filters.indexOf(this.emptyMessage()); 36037 var container = $$5(this.s.dtPane.table().container()); 36038 // If null index is found then search for empty cells as a filter. 36039 if (nullIndex > -1) { 36040 filters[nullIndex] = ''; 36041 } 36042 // If a filter has been applied then outline the respective pane, remove it when it no longer is. 36043 if (filters.length > 0) { 36044 container.addClass(this.classes.selected); 36045 } 36046 else if (filters.length === 0) { 36047 container.removeClass(this.classes.selected); 36048 } 36049 this.s.updating = updating; 36050 }; 36051 SearchPane.version = '2.1.2'; 36052 SearchPane.classes = { 36053 bordered: 'dtsp-bordered', 36054 buttonGroup: 'dtsp-buttonGroup', 36055 buttonSub: 'dtsp-buttonSub', 36056 caret: 'dtsp-caret', 36057 clear: 'dtsp-clear', 36058 clearAll: 'dtsp-clearAll', 36059 clearButton: 'clearButton', 36060 collapseAll: 'dtsp-collapseAll', 36061 collapseButton: 'dtsp-collapseButton', 36062 container: 'dtsp-searchPane', 36063 countButton: 'dtsp-countButton', 36064 disabledButton: 'dtsp-disabledButton', 36065 hidden: 'dtsp-hidden', 36066 hide: 'dtsp-hide', 36067 layout: 'dtsp-', 36068 name: 'dtsp-name', 36069 nameButton: 'dtsp-nameButton', 36070 nameCont: 'dtsp-nameCont', 36071 narrow: 'dtsp-narrow', 36072 paneButton: 'dtsp-paneButton', 36073 paneInputButton: 'dtsp-paneInputButton', 36074 pill: 'dtsp-pill', 36075 rotated: 'dtsp-rotated', 36076 search: 'dtsp-search', 36077 searchCont: 'dtsp-searchCont', 36078 searchIcon: 'dtsp-searchIcon', 36079 searchLabelCont: 'dtsp-searchButtonCont', 36080 selected: 'dtsp-selected', 36081 smallGap: 'dtsp-smallGap', 36082 subRow1: 'dtsp-subRow1', 36083 subRow2: 'dtsp-subRow2', 36084 subRowsContainer: 'dtsp-subRowsContainer', 36085 title: 'dtsp-title', 36086 topRow: 'dtsp-topRow' 36087 }; 36088 // Define SearchPanes default options 36089 SearchPane.defaults = { 36090 clear: true, 36091 collapse: true, 36092 combiner: 'or', 36093 container: function (dt) { 36094 return dt.table().container(); 36095 }, 36096 controls: true, 36097 dtOpts: {}, 36098 emptyMessage: null, 36099 hideCount: false, 36100 i18n: { 36101 clearPane: '×', 36102 count: '{total}', 36103 emptyMessage: '<em>No data</em>' 36104 }, 36105 initCollapsed: false, 36106 layout: 'auto', 36107 name: undefined, 36108 orderable: true, 36109 orthogonal: { 36110 display: 'display', 36111 filter: 'filter', 36112 hideCount: false, 36113 search: 'filter', 36114 show: undefined, 36115 sort: 'sort', 36116 threshold: 0.6, 36117 type: 'type', 36118 viewCount: true 36119 }, 36120 preSelect: [], 36121 threshold: 0.6, 36122 viewCount: true 36123 }; 36124 return SearchPane; 36125 }()); 36126 36127 var __extends$4 = (window && window.__extends) || (function () { 36128 var extendStatics = function (d, b) { 36129 extendStatics = Object.setPrototypeOf || 36130 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 36131 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 36132 return extendStatics(d, b); 36133 }; 36134 return function (d, b) { 36135 extendStatics(d, b); 36136 function __() { this.constructor = d; } 36137 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 36138 }; 36139 })(); 36140 var SearchPaneST = /** @class */ (function (_super) { 36141 __extends$4(SearchPaneST, _super); 36142 function SearchPaneST(paneSettings, opts, index, panesContainer, panes) { 36143 return _super.call(this, paneSettings, opts, index, panesContainer, panes) || this; 36144 } 36145 /** 36146 * Populates the SearchPane based off of the data that has been recieved from the server 36147 * 36148 * This method overrides SearchPane's _serverPopulate() method 36149 * 36150 * @param dataIn The data that has been sent from the server 36151 */ 36152 SearchPaneST.prototype._serverPopulate = function (dataIn) { 36153 this.s.rowData.binsShown = {}; 36154 this.s.rowData.arrayFilter = []; 36155 if (dataIn.tableLength !== undefined) { 36156 this.s.tableLength = dataIn.tableLength; 36157 this.s.rowData.totalOptions = this.s.tableLength; 36158 } 36159 else if (this.s.tableLength === null || this.s.dt.rows()[0].length > this.s.tableLength) { 36160 this.s.tableLength = this.s.dt.rows()[0].length; 36161 this.s.rowData.totalOptions = this.s.tableLength; 36162 } 36163 var colTitle = this.s.dt.column(this.s.index).dataSrc(); 36164 // If there is SP data for this column add it to the data array and bin 36165 if (dataIn.searchPanes.options[colTitle] !== undefined) { 36166 for (var _i = 0, _a = dataIn.searchPanes.options[colTitle]; _i < _a.length; _i++) { 36167 var dataPoint = _a[_i]; 36168 this.s.rowData.arrayFilter.push({ 36169 display: dataPoint.label, 36170 filter: dataPoint.value, 36171 shown: +dataPoint.count, 36172 sort: dataPoint.label, 36173 total: +dataPoint.total, 36174 type: dataPoint.label 36175 }); 36176 this.s.rowData.binsShown[dataPoint.value] = +dataPoint.count; 36177 this.s.rowData.bins[dataPoint.value] = +dataPoint.total; 36178 } 36179 } 36180 var binLength = Object.keys(this.s.rowData.bins).length; 36181 var uniqueRatio = this._uniqueRatio(binLength, this.s.tableLength); 36182 // Don't show the pane if there isnt enough variance in the data, or there is only 1 entry for that pane 36183 if (!this.s.colOpts.show && 36184 this.s.displayed === false && 36185 ((this.s.colOpts.show === undefined && this.s.colOpts.threshold === null ? 36186 uniqueRatio > this.c.threshold : 36187 uniqueRatio > this.s.colOpts.threshold) || 36188 this.s.colOpts.show !== true && binLength <= 1)) { 36189 this.dom.container.addClass(this.classes.hidden); 36190 this.s.displayed = false; 36191 return; 36192 } 36193 // Store the original data 36194 this.s.rowData.arrayOriginal = this.s.rowData.arrayFilter; 36195 this.s.rowData.binsOriginal = this.s.rowData.bins; 36196 // Flag this pane as being displayed 36197 this.s.displayed = true; 36198 // If the pane exists 36199 if (this.s.dtPane) { 36200 // Not the selections that have been made and remove all of the rows 36201 var selected = this.s.serverSelect; 36202 this.s.dtPane.rows().remove(); 36203 // Add the rows that are to be shown into the pane 36204 for (var _b = 0, _c = this.s.rowData.arrayFilter; _b < _c.length; _b++) { 36205 var data = _c[_b]; 36206 if (this._shouldAddRow(data)) { 36207 var row = this.addRow(data.display, data.filter, data.sort, data.type); 36208 // Select the row if it was selected before 36209 for (var i = 0; i < selected.length; i++) { 36210 var selection = selected[i]; 36211 if (selection.filter === data.filter) { 36212 // This flag stops another request being made to the server 36213 this.s.serverSelecting = true; 36214 row.select(); 36215 this.s.serverSelecting = false; 36216 // Remove the selection from the to select list and add it to the selected list 36217 selected.splice(i, 1); 36218 this.s.selections.push(data.filter); 36219 break; 36220 } 36221 } 36222 } 36223 } 36224 // Remake any selections that are no longer present 36225 for (var _d = 0, selected_1 = selected; _d < selected_1.length; _d++) { 36226 var selection = selected_1[_d]; 36227 for (var _e = 0, _f = this.s.rowData.arrayOriginal; _e < _f.length; _e++) { 36228 var data = _f[_e]; 36229 if (data.filter === selection.filter) { 36230 var row = this.addRow(data.display, data.filter, data.sort, data.type); 36231 this.s.serverSelecting = true; 36232 row.select(); 36233 this.s.serverSelecting = false; 36234 this.s.selections.push(data.filter); 36235 } 36236 } 36237 } 36238 // Store the selected rows 36239 this.s.serverSelect = this.s.dtPane.rows({ selected: true }).data().toArray(); 36240 // Update the pane 36241 this.s.dtPane.draw(); 36242 } 36243 }; 36244 /** 36245 * This method updates the rows and their data within the SearchPanes 36246 * 36247 * SearchPaneCascade overrides this method 36248 */ 36249 SearchPaneST.prototype.updateRows = function () { 36250 if (!this.s.dt.page.info().serverSide) { 36251 // Get the latest count values from the table 36252 this.s.rowData.binsShown = {}; 36253 for (var _i = 0, _a = this.s.dt.rows({ search: 'applied' }).indexes().toArray(); _i < _a.length; _i++) { 36254 var index = _a[_i]; 36255 this._updateShown(index, this.s.dt.settings()[0], this.s.rowData.binsShown); 36256 } 36257 } 36258 // Update the rows data to show the current counts 36259 for (var _b = 0, _c = this.s.dtPane.rows().data().toArray(); _b < _c.length; _b++) { 36260 var row = _c[_b]; 36261 row.shown = typeof this.s.rowData.binsShown[row.filter] === 'number' ? 36262 this.s.rowData.binsShown[row.filter] : 36263 0; 36264 this.s.dtPane.row(row.index).data(row);
36265 } 36266 // Show updates in the pane 36267 this.s.dtPane.draw(); 36268 this.s.dtPane.table().node().parentNode.scrollTop = this.s.scrollTop; 36269 }; 36270 /** 36271 * Remove functionality from makeSelection - needs to be more advanced when tracking selections 36272 */ 36273 SearchPaneST.prototype._makeSelection = function () { 36274 return; 36275 }; 36276 /** 36277 * Blank method to remove reloading of selected rows - needs to be more advanced when tracking selections 36278 */ 36279 SearchPaneST.prototype._reloadSelect = function () { 36280 return; 36281 }; 36282 /** 36283 * Decides if a row should be added when being added from the server 36284 * 36285 * Overridden by SearchPaneCascade 36286 * 36287 * @param data the row data 36288 * @returns boolean indicating if the row should be added or not 36289 */ 36290 // eslint-disable-next-line @typescript-eslint/no-unused-vars 36291 SearchPaneST.prototype._shouldAddRow = function (data) { 36292 return true; 36293 }; 36294 /** 36295 * Updates the server selection list where appropriate 36296 */ 36297 SearchPaneST.prototype._updateSelection = function () { 36298 if (this.s.dt.page.info().serverSide && !this.s.updating && !this.s.serverSelecting) { 36299 this.s.serverSelect = this.s.dtPane.rows({ selected: true }).data().toArray(); 36300 } 36301 }; 36302 /** 36303 * Used when binning the data for a column 36304 * 36305 * @param rowIdx The current row that is to be added to the bins 36306 * @param settings The datatables settings object 36307 * @param bins The bins object that is to be incremented 36308 */ 36309 SearchPaneST.prototype._updateShown = function (rowIdx, settings, bins) { 36310 if (bins === void 0) { bins = this.s.rowData.binsShown; } 36311 var orth = typeof this.s.colOpts.orthogonal === 'string' 36312 ? this.s.colOpts.orthogonal 36313 : this.s.colOpts.orthogonal.search; 36314 var filter = settings.oApi._fnGetCellData(settings, rowIdx, this.s.index, orth); 36315 var add = function (f) { 36316 if (!bins[f]) { 36317 bins[f] = 1; 36318 } 36319 else { 36320 bins[f]++; 36321 } 36322 }; 36323 if (Array.isArray(filter)) { 36324 for (var _i = 0, filter_1 = filter; _i < filter_1.length; _i++) { 36325 var f = filter_1[_i]; 36326 add(f); 36327 } 36328 } 36329 else { 36330 add(filter); 36331 } 36332 }; 36333 return SearchPaneST; 36334 }(SearchPane)); 36335 36336 var __extends$3 = (window && window.__extends) || (function () { 36337 var extendStatics = function (d, b) { 36338 extendStatics = Object.setPrototypeOf || 36339 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 36340 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 36341 return extendStatics(d, b); 36342 }; 36343 return function (d, b) { 36344 extendStatics(d, b); 36345 function __() { this.constructor = d; } 36346 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 36347 }; 36348 })(); 36349 var $$4; 36350 function setJQuery$3(jq) { 36351 $$4 = jq; 36352 } 36353 var SearchPaneViewTotal = /** @class */ (function (_super) { 36354 __extends$3(SearchPaneViewTotal, _super); 36355 function SearchPaneViewTotal(paneSettings, opts, index, panesContainer, panes) { 36356 var _this = this; 36357 var override = { 36358 i18n: { 36359 countFiltered: '{shown} ({total})' 36360 } 36361 }; 36362 _this = _super.call(this, paneSettings, $$4.extend(override, opts), index, panesContainer, panes) || this; 36363 return _this; 36364 } 36365 /** 36366 * Gets the message that is to be used to indicate the count for each SearchPane row 36367 * 36368 * This method overrides _getMessage() in SearchPane and is overridden by SearchPaneCascadeViewTotal 36369 * 36370 * @param row The row object that is being processed 36371 * @returns string - the message that is to be shown for the count of each entry 36372 */ 36373 SearchPaneViewTotal.prototype._getMessage = function (row) { 36374 var countMessage = this.s.dt.i18n('searchPanes.count', this.c.i18n.count); 36375 var filteredMessage = this.s.dt.i18n('searchPanes.countFiltered', this.c.i18n.countFiltered); 36376 return (this.s.filteringActive ? filteredMessage : countMessage) 36377 .replace(/{total}/g, row.total) 36378 .replace(/{shown}/g, row.shown); 36379 }; 36380 /** 36381 * Overrides the blank method in SearchPane to return the number of times a given value is currently being displayed 36382 * 36383 * @param filter The filter value 36384 * @returns number - The number of times the value is shown 36385 */ 36386 SearchPaneViewTotal.prototype._getShown = function (filter) { 36387 return this.s.rowData.binsShown && this.s.rowData.binsShown[filter] ? 36388 this.s.rowData.binsShown[filter] : 36389 0; 36390 }; 36391 return SearchPaneViewTotal; 36392 }(SearchPaneST)); 36393 36394 var __extends$2 = (window && window.__extends) || (function () { 36395 var extendStatics = function (d, b) { 36396 extendStatics = Object.setPrototypeOf || 36397 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 36398 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 36399 return extendStatics(d, b); 36400 }; 36401 return function (d, b) { 36402 extendStatics(d, b); 36403 function __() { this.constructor = d; } 36404 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 36405 }; 36406 })(); 36407 var $$3; 36408 function setJQuery$2(jq) { 36409 $$3 = jq; 36410 } 36411 var SearchPaneCascade = /** @class */ (function (_super) { 36412 __extends$2(SearchPaneCascade, _super); 36413 function SearchPaneCascade(paneSettings, opts, index, panesContainer, panes) { 36414 var _this = this; 36415 var override = { 36416 i18n: { 36417 count: '{shown}' 36418 } 36419 }; 36420 _this = _super.call(this, paneSettings, $$3.extend(override, opts), index, panesContainer, panes) || this; 36421 return _this; 36422 } 36423 /** 36424 * This method updates the rows and their data within the SearchPanes 36425 * 36426 * This overrides the method in SearchPane 36427 */ 36428 SearchPaneCascade.prototype.updateRows = function () { 36429 // Note the currently selected values in the pane and remove all of the rows 36430 var selected = this.s.dtPane.rows({ selected: true }).data().toArray(); 36431 if (this.s.colOpts.options || 36432 this.s.customPaneSettings && this.s.customPaneSettings.options) { 36433 // If there are custom options set or it is a custom pane then get them 36434 this._getComparisonRows(); 36435 var rows = this.s.dtPane.rows().toArray()[0]; 36436 for (var i = 0; i < rows.length; i++) { 36437 var row = this.s.dtPane.row(rows[i]); 36438 var rowData = row.data(); 36439 if (rowData === undefined) { 36440 continue; 36441 } 36442 if (rowData.shown === 0) { 36443 row.remove(); 36444 rows = this.s.dtPane.rows().toArray()[0]; 36445 i--; 36446 continue; 36447 } 36448 for (var _i = 0, selected_1 = selected; _i < selected_1.length; _i++) { 36449 var selection = selected_1[_i]; 36450 if (rowData.filter === selection.filter) { 36451 row.select(); 36452 selected.splice(i, 1); 36453 this.s.selections.push(rowData.filter); 36454 break; 36455 } 36456 } 36457 } 36458 } 36459 else { 36460 if (!this.s.dt.page.info().serverSide) { 36461 // Get the latest count values from the table 36462 this._activePopulatePane(); 36463 this.s.rowData.binsShown = {}; 36464 for (var _a = 0, _b = this.s.dt.rows({ search: 'applied' }).indexes().toArray(); _a < _b.length; _a++) { 36465 var index = _b[_a]; 36466 this._updateShown(index, this.s.dt.settings()[0], this.s.rowData.binsShown); 36467 } 36468 } 36469 this.s.dtPane.rows().remove(); 36470 // Go over all of the rows that could be displayed 36471 for (var _c = 0, _d = this.s.rowData.arrayFilter; _c < _d.length; _c++) { 36472 var data = _d[_c]; 36473 // Cascade - If there are no rows present in the table don't show the option 36474 if (data.shown === 0) { 36475 continue; 36476 } 36477 // Add the row to the pane 36478 var row = this.addRow(data.display, data.filter, data.sort, data.type, undefined); 36479 // Check if this row was selected 36480 for (var i = 0; i < selected.length; i++) { 36481 var selection = selected[i]; 36482 if (selection.filter === data.filter) { 36483 row.select(); 36484 // Remove the row from the to find list and then add it to the selections list 36485 selected.splice(i, 1); 36486 this.s.selections.push(data.filter); 36487 break; 36488 } 36489 } 36490 } 36491 // Add all of the rows that were previously selected but aren't any more 36492 for (var _e = 0, selected_2 = selected; _e < selected_2.length; _e++) { 36493 var selection = selected_2[_e]; 36494 for (var _f = 0, _g = this.s.rowData.arrayOriginal; _f < _g.length; _f++) { 36495 var data = _g[_f]; 36496 if (data.filter === selection.filter) { 36497 var row = this.addRow(data.display, data.filter, data.sort, data.type, undefined); 36498 row.select(); 36499 this.s.selections.push(data.filter); 36500 } 36501 } 36502 } 36503 } 36504 // Show updates in the pane 36505 this.s.dtPane.draw(); 36506 this.s.dtPane.table().node().parentNode.scrollTop = this.s.scrollTop; 36507 // If client side updated the tables results 36508 if (!this.s.dt.page.info().serverSide) { 36509 this.s.dt.draw(false); 36510 } 36511 }; 36512 /** 36513 * Fill the array with the values that are currently being displayed in the table 36514 */ 36515 SearchPaneCascade.prototype._activePopulatePane = function () { 36516 this.s.rowData.arrayFilter = []; 36517 this.s.rowData.bins = {}; 36518 var settings = this.s.dt.settings()[0]; 36519 if (!this.s.dt.page.info().serverSide) { 36520 for (var _i = 0, _a = this.s.dt.rows({ search: 'applied' }).indexes().toArray(); _i < _a.length; _i++) { 36521 var index = _a[_i]; 36522 this._populatePaneArray(index, this.s.rowData.arrayFilter, settings); 36523 } 36524 } 36525 }; 36526 SearchPaneCascade.prototype._getComparisonRows = function () { 36527 // Find the appropriate options depending on whether this is a pane for a specific column or a custom pane 36528 var options = this.s.colOpts.options 36529 ? this.s.colOpts.options 36530 : this.s.customPaneSettings && this.s.customPaneSettings.options 36531 ? this.s.customPaneSettings.options 36532 : undefined; 36533 if (options === undefined) { 36534 return; 36535 } 36536 var allRows = this.s.dt.rows(); 36537 var shownRows = this.s.dt.rows({ search: 'applied' });
36538 var tableValsTotal = allRows.data().toArray(); 36539 var tableValsShown = shownRows.data().toArray(); 36540 var rows = []; 36541 // Clear all of the other rows from the pane, only custom options are to be displayed when they are defined 36542 this.s.dtPane.clear(); 36543 this.s.indexes = []; 36544 for (var _i = 0, options_1 = options; _i < options_1.length; _i++) { 36545 var comp = options_1[_i]; 36546 // Initialise the object which is to be placed in the row 36547 var insert = comp.label !== '' ? 36548 comp.label : 36549 this.emptyMessage(); 36550 var comparisonObj = { 36551 className: comp.className, 36552 display: insert, 36553 filter: typeof comp.value === 'function' ? comp.value : [], 36554 shown: 0, 36555 sort: insert, 36556 total: 0, 36557 type: insert 36558 }; 36559 // If a custom function is in place 36560 if (typeof comp.value === 'function') { 36561 // Count the number of times the function evaluates to true for the original data in the Table 36562 for (var i = 0; i < tableValsTotal.length; i++) { 36563 if (comp.value.call(this.s.dt, tableValsTotal[i], allRows[0][i])) { 36564 comparisonObj.total++; 36565 } 36566 } 36567 for (var i = 0; i < tableValsShown.length; i++) { 36568 if (comp.value.call(this.s.dt, tableValsShown[i], shownRows[0][i])) { 36569 comparisonObj.shown++; 36570 } 36571 } 36572 // Update the comparisonObj 36573 if (typeof comparisonObj.filter !== 'function') { 36574 comparisonObj.filter.push(comp.filter); 36575 } 36576 } 36577 rows.push(this.addRow(comparisonObj.display, comparisonObj.filter, comparisonObj.sort, comparisonObj.type, comparisonObj.className, comparisonObj.total, comparisonObj.shown)); 36578 } 36579 return rows; 36580 }; 36581 /** 36582 * Gets the message that is to be used to indicate the count for each SearchPane row 36583 * 36584 * This method overrides _getMessage() in SearchPane and is overridden by SearchPaneCascadeViewTotal 36585 * 36586 * @param row The row object that is being processed 36587 * @returns string - the message that is to be shown for the count of each entry 36588 */ 36589 SearchPaneCascade.prototype._getMessage = function (row) { 36590 return this.s.dt.i18n('searchPanes.count', this.c.i18n.count) 36591 .replace(/{total}/g, row.total) 36592 .replace(/{shown}/g, row.shown); 36593 }; 36594 /** 36595 * Overrides the blank method in SearchPane to return the number of times a given value is currently being displayed 36596 * 36597 * @param filter The filter value 36598 * @returns number - The number of times the value is shown 36599 */ 36600 SearchPaneCascade.prototype._getShown = function (filter) { 36601 return this.s.rowData.binsShown && this.s.rowData.binsShown[filter] ? 36602 this.s.rowData.binsShown[filter] : 36603 0; 36604 }; 36605 /** 36606 * Decides if a row should be added when being added from the server 36607 * 36608 * Overrides method in by SearchPaneST 36609 * 36610 * @param data the row data 36611 * @returns boolean indicating if the row should be added or not 36612 */ 36613 SearchPaneCascade.prototype._shouldAddRow = function (data) { 36614 return data.shown > 0; 36615 }; 36616 return SearchPaneCascade; 36617 }(SearchPaneST)); 36618 36619 var __extends$1 = (window && window.__extends) || (function () { 36620 var extendStatics = function (d, b) { 36621 extendStatics = Object.setPrototypeOf || 36622 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 36623 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 36624 return extendStatics(d, b); 36625 }; 36626 return function (d, b) { 36627 extendStatics(d, b); 36628 function __() { this.constructor = d; } 36629 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 36630 }; 36631 })(); 36632 var $$2; 36633 function setJQuery$1(jq) { 36634 $$2 = jq; 36635 } 36636 var SearchPaneCascadeViewTotal = /** @class */ (function (_super) { 36637 __extends$1(SearchPaneCascadeViewTotal, _super); 36638 function SearchPaneCascadeViewTotal(paneSettings, opts, index, panesContainer, panes) { 36639 var _this = this; 36640 var override = { 36641 i18n: { 36642 count: '{total}', 36643 countFiltered: '{shown} ({total})' 36644 } 36645 }; 36646 _this = _super.call(this, paneSettings, $$2.extend(override, opts), index, panesContainer, panes) || this; 36647 return _this; 36648 } 36649 /** 36650 * Fill the array with the values that are currently being displayed in the table 36651 * 36652 * This method overrides _activePopulatePane() in SearchPaneCascade 36653 */ 36654 SearchPaneCascadeViewTotal.prototype._activePopulatePane = function () { 36655 this.s.rowData.arrayFilter = []; 36656 this.s.rowData.binsShown = {}; 36657 var settings = this.s.dt.settings()[0]; 36658 if (!this.s.dt.page.info().serverSide) { 36659 for (var _i = 0, _a = this.s.dt.rows({ search: 'applied' }).indexes().toArray(); _i < _a.length; _i++) { 36660 var index = _a[_i]; 36661 this._populatePaneArray(index, this.s.rowData.arrayFilter, settings, this.s.rowData.binsShown); 36662 } 36663 } 36664 }; 36665 /** 36666 * Gets the message that is to be used to indicate the count for each SearchPane row 36667 * 36668 * This method overrides _getMessage() in SearchPaneCascade 36669 * 36670 * @param row The row object that is being processed 36671 * @returns string - the message that is to be shown for the count of each entry 36672 */ 36673 SearchPaneCascadeViewTotal.prototype._getMessage = function (row) { 36674 var countMessage = this.s.dt.i18n('searchPanes.count', this.c.i18n.count); 36675 var filteredMessage = this.s.dt.i18n('searchPanes.countFiltered', this.c.i18n.countFiltered); 36676 return (this.s.filteringActive ? filteredMessage : countMessage) 36677 .replace(/{total}/g, row.total) 36678 .replace(/{shown}/g, row.shown); 36679 }; 36680 return SearchPaneCascadeViewTotal; 36681 }(SearchPaneCascade));
36682 36683 var $$1; 36684 var dataTable$1; 36685 function setJQuery(jq) { 36686 $$1 = jq; 36687 dataTable$1 = jq.fn.dataTable; 36688 } 36689 var SearchPanes = /** @class */ (function () { 36690 function SearchPanes(paneSettings, opts, fromPreInit, paneClass) { 36691 var _this = this; 36692 if (fromPreInit === void 0) { fromPreInit = false; } 36693 if (paneClass === void 0) { paneClass = SearchPane; } 36694 // Check that the required version of DataTables is included 36695 if (!dataTable$1 || !dataTable$1.versionCheck || !dataTable$1.versionCheck('1.10.0')) { 36696 throw new Error('SearchPane requires DataTables 1.10 or newer'); 36697 } 36698 // Check that Select is included 36699 // eslint-disable-next-line no-extra-parens 36700 if (!dataTable$1.select) { 36701 throw new Error('SearchPane requires Select'); 36702 } 36703 var table = new dataTable$1.Api(paneSettings); 36704 this.classes = $$1.extend(true, {}, SearchPanes.classes); 36705 // Get options from user 36706 this.c = $$1.extend(true, {}, SearchPanes.defaults, opts); 36707 // Add extra elements to DOM object including clear 36708 this.dom = { 36709 clearAll: $$1('<button type="button"/>') 36710 .addClass(this.classes.clearAll) 36711 .html(table.i18n('searchPanes.clearMessage', this.c.i18n.clearMessage)), 36712 collapseAll: $$1('<button type="button"/>') 36713 .addClass(this.classes.collapseAll) 36714 .html(table.i18n('searchPanes.collapseMessage', this.c.i18n.collapseMessage)), 36715 container: $$1('<div/>').addClass(this.classes.panes).html(table.i18n('searchPanes.loadMessage', this.c.i18n.loadMessage)), 36716 emptyMessage: $$1('<div/>').addClass(this.classes.emptyMessage), 36717 panes: $$1('<div/>').addClass(this.classes.container), 36718 showAll: $$1('<button type="button"/>') 36719 .addClass(this.classes.showAll) 36720 .addClass(this.classes.disabledButton) 36721 .attr('disabled', 'true') 36722 .html(table.i18n('searchPanes.showMessage', this.c.i18n.showMessage)), 36723 title: $$1('<div/>').addClass(this.classes.title), 36724 titleRow: $$1('<div/>').addClass(this.classes.titleRow) 36725 }; 36726 this.s = { 36727 colOpts: [], 36728 dt: table, 36729 filterCount: 0, 36730 minPaneWidth: 260.0, 36731 page: 0, 36732 paging: false, 36733 pagingST: false, 36734 paneClass: paneClass, 36735 panes: [], 36736 selectionList: [], 36737 serverData: {}, 36738 stateRead: false, 36739 updating: false 36740 }; 36741 // Do not reinitialise if already initialised on table 36742 if (table.settings()[0]._searchPanes) { 36743 return; 36744 } 36745 this._getState(); 36746 if (this.s.dt.page.info().serverSide) { 36747 var hostSettings = this.s.dt.settings()[0]; 36748 // Listener to get the data into the server request before it is made 36749 this.s.dt.on('preXhr.dtsps', function (e, settings, data) { 36750 if (hostSettings !== settings) { 36751 return; 36752 } 36753 if (data.searchPanes === undefined) { 36754 data.searchPanes = {}; 36755 } 36756 if (data.searchPanes_null === undefined) { 36757 data.searchPanes_null = {}; 36758 } 36759 var src; 36760 for (var _i = 0, _a = _this.s.selectionList; _i < _a.length; _i++) { 36761 var selection = _a[_i]; 36762 src = _this.s.dt.column(selection.column).dataSrc(); 36763 if (data.searchPanes[src] === undefined) { 36764 data.searchPanes[src] = {}; 36765 } 36766 if (data.searchPanes_null[src] === undefined) { 36767 data.searchPanes_null[src] = {}; 36768 } 36769 for (var i = 0; i < selection.rows.length; i++) { 36770 data.searchPanes[src][i] = selection.rows[i]; 36771 if (data.searchPanes[src][i] === null) { 36772 data.searchPanes_null[src][i] = true; 36773 } 36774 } 36775 } 36776 if (_this.s.selectionList.length > 0) { 36777 data.searchPanesLast = src; 36778 } 36779 // Config options that will change how the querying is done 36780 data.searchPanes_options = { 36781 cascade: _this.c.cascadePanes, 36782 viewCount: _this.c.viewCount, 36783 viewTotal: _this.c.viewTotal 36784 }; 36785 }); 36786 } 36787 this._setXHR(); 36788 table.settings()[0]._searchPanes = this; 36789 if (this.s.dt.settings()[0]._bInitComplete || fromPreInit) { 36790 this._paneDeclare(table, paneSettings, opts); 36791 } 36792 else { 36793 table.one('preInit.dtsps', function () { 36794 _this._paneDeclare(table, paneSettings, opts); 36795 }); 36796 } 36797 return this; 36798 } 36799 /** 36800 * Clear the selections of all of the panes 36801 */ 36802 SearchPanes.prototype.clearSelections = function () { 36803 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 36804 var pane = _a[_i]; 36805 if (pane.s.dtPane) { 36806 pane.s.scrollTop = pane.s.dtPane.table().node().parentNode.scrollTop; 36807 } 36808 } 36809 // Load in all of the searchBoxes in the documents 36810 var searches = this.dom.container.find('.' + this.classes.search.replace(/\s+/g, '.')); 36811 // For each searchBox set the input text to be empty and then trigger 36812 // an input on them so that they no longer filter the panes 36813 searches.each(function () { 36814 $$1(this).val('').trigger('input'); 36815 }); 36816 // Clear the selectionList 36817 this.s.selectionList = []; 36818 var returnArray = []; 36819 for (var _b = 0, _c = this.s.panes; _b < _c.length; _b++) { 36820 var pane = _c[_b]; 36821 if (pane.s.dtPane) { 36822 returnArray.push(pane.clearPane()); 36823 } 36824 } 36825 return returnArray; 36826 }; 36827 /** 36828 * returns the container node for the searchPanes 36829 */ 36830 SearchPanes.prototype.getNode = function () { 36831 return this.dom.container; 36832 }; 36833 /** 36834 * rebuilds all of the panes 36835 */ 36836 SearchPanes.prototype.rebuild = function (targetIdx, maintainSelection) { 36837 if (targetIdx === void 0) { targetIdx = false; } 36838 if (maintainSelection === void 0) { maintainSelection = false; } 36839 this.dom.emptyMessage.detach(); 36840 // As a rebuild from scratch is required, empty the searchpanes container. 36841 if (targetIdx === false) { 36842 this.dom.panes.empty(); 36843 } 36844 // Rebuild each pane individually, if a specific pane has been selected then only rebuild that one 36845 var returnArray = []; 36846 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 36847 var pane = _a[_i]; 36848 if (targetIdx === false || pane.s.index === targetIdx) { 36849 pane.clearData(); 36850 pane.rebuildPane(this.s.dt.page.info().serverSide ? 36851 this.s.serverData : 36852 undefined, maintainSelection); 36853 this.dom.panes.append(pane.dom.container); 36854 returnArray.push(pane); 36855 } 36856 } 36857 this._updateSelection(); 36858 // Attach panes, clear buttons, and title bar to the document 36859 this._updateFilterCount(); 36860 this._attachPaneContainer(); 36861 this._initSelectionListeners(false); 36862 // If the selections are to be maintained, then it is safe to assume that paging is also to be maintained 36863 // Otherwise, the paging should be reset 36864 this.s.dt.draw(!maintainSelection); 36865 // Resize the panes incase there has been a change 36866 this.resizePanes(); 36867 // If a single pane has been rebuilt then return only that pane 36868 return returnArray.length === 1 ? returnArray[0] : returnArray; 36869 }; 36870 /** 36871 * Resizes all of the panes 36872 */ 36873 SearchPanes.prototype.resizePanes = function () { 36874 if (this.c.layout === 'auto') { 36875 var contWidth = $$1(this.s.dt.searchPanes.container()).width(); 36876 var target = Math.floor(contWidth / this.s.minPaneWidth); // The neatest number of panes per row 36877 var highest_1 = 1; 36878 var highestmod_1 = 0; 36879 // Get the indexes of all of the displayed panes 36880 var dispIndex = []; 36881 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 36882 var pane = _a[_i]; 36883 if (pane.s.displayed) { 36884 dispIndex.push(pane.s.index); 36885 } 36886 } 36887 var displayCount = dispIndex.length; 36888 // If the neatest number is the number we have then use this. 36889 if (target === displayCount) {
36890 highest_1 = target; 36891 } 36892 else { 36893 // Go from the target down and find the value with the most panes left over, this will be the best fit 36894 for (var ppr = target; ppr > 1; ppr--) { 36895 var rem = displayCount % ppr; 36896 if (rem === 0) { 36897 highest_1 = ppr; 36898 highestmod_1 = 0; 36899 break; 36900 } 36901 // If there are more left over at this amount of panes per row (ppr) 36902 // then it fits better so new values 36903 else if (rem > highestmod_1) { 36904 highest_1 = ppr; 36905 highestmod_1 = rem; 36906 } 36907 } 36908 } 36909 // If there is a perfect fit then none are to be wider 36910 var widerIndexes_1 = highestmod_1 !== 0 ? dispIndex.slice(dispIndex.length - highestmod_1, dispIndex.length) : []; 36911 this.s.panes.forEach(function (pane) { 36912 // Resize the pane with the new layout 36913 if (pane.s.displayed) { 36914 pane.resize('columns-' + (!widerIndexes_1.includes(pane.s.index) ? highest_1 : highestmod_1)); 36915 } 36916 }); 36917 } 36918 else { 36919 for (var _b = 0, _c = this.s.panes; _b < _c.length; _b++) { 36920 var pane = _c[_b]; 36921 pane.adjustTopRow(); 36922 } 36923 } 36924 return this; 36925 }; 36926 /** 36927 * Holder method that is userd in SearchPanesST to set listeners that have an effect on other panes 36928 * 36929 * @param isPreselect boolean to indicate if the preselect array is to override the current selection list. 36930 */ 36931 SearchPanes.prototype._initSelectionListeners = function (isPreselect) { 36932 return; 36933 }; 36934 /** 36935 * Blank method that is overridden in SearchPanesST to retrieve the totals from the server data 36936 */ 36937 SearchPanes.prototype._serverTotals = function () { 36938 return; 36939 }; 36940 /** 36941 * Set's the xhr listener so that SP can extact appropriate data from the response 36942 */ 36943 SearchPanes.prototype._setXHR = function () { 36944 var _this = this; 36945 var hostSettings = this.s.dt.settings()[0]; 36946 var run = function (json) { 36947 if (json && json.searchPanes && json.searchPanes.options) { 36948 _this.s.serverData = json; 36949 _this.s.serverData.tableLength = json.recordsTotal; 36950 _this._serverTotals(); 36951 } 36952 }; 36953 // We are using the xhr event to rebuild the panes if required due to viewTotal being enabled 36954 // If viewTotal is not enabled then we simply update the data from the server 36955 this.s.dt.on('xhr.dtsps', function (e, settings, json) { 36956 if (hostSettings === settings) { 36957 run(json); 36958 } 36959 }); 36960 // Account for the initial JSON fetch having already completed 36961 run(this.s.dt.ajax.json()); 36962 }; 36963 /** 36964 * Set's the function that is to be performed when a state is loaded 36965 * 36966 * Overridden by the method in SearchPanesST 36967 */ 36968 SearchPanes.prototype._stateLoadListener = function () { 36969 var _this = this; 36970 var hostSettings = this.s.dt.settings()[0]; 36971 this.s.dt.on('stateLoadParams.dtsps', function (e, settings, data) { 36972 if (data.searchPanes === undefined || settings !== hostSettings) { 36973 return; 36974 } 36975 _this.clearSelections(); 36976 // Set the selection list for the panes so that the correct 36977 // rows can be reselected and in the right order 36978 _this.s.selectionList = 36979 data.searchPanes.selectionList ? 36980 data.searchPanes.selectionList : 36981 []; 36982 // Find the panes that match from the state and the actual instance 36983 if (data.searchPanes.panes) { 36984 for (var _i = 0, _a = data.searchPanes.panes; _i < _a.length; _i++) { 36985 var loadedPane = _a[_i]; 36986 for (var _b = 0, _c = _this.s.panes; _b < _c.length; _b++) { 36987 var pane = _c[_b]; 36988 if (loadedPane.id === pane.s.index && pane.s.dtPane) { 36989 // Set the value of the searchbox 36990 pane.dom.searchBox.val(loadedPane.searchTerm); 36991 // Set the value of the order 36992 pane.s.dtPane.order(loadedPane.order); 36993 } 36994 } 36995 } 36996 } 36997 _this._makeSelections(_this.s.selectionList); 36998 }); 36999 }; 37000 /** 37001 * Updates the selectionList when cascade is not in place 37002 * 37003 * Overridden in SearchPanesST 37004 */ 37005 SearchPanes.prototype._updateSelection = function () { 37006 this.s.selectionList = []; 37007 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37008 var pane = _a[_i]; 37009 if (pane.s.dtPane) { 37010 var rows = pane.s.dtPane.rows({ selected: true }).data().toArray().map(function (el) { return el.filter; }); 37011 if (rows.length) { 37012 this.s.selectionList.push({ 37013 column: pane.s.index, 37014 rows: rows 37015 }); 37016 } 37017 } 37018 } 37019 }; 37020 /** 37021 * Attach the panes, buttons and title to the document 37022 */ 37023 SearchPanes.prototype._attach = function () { 37024 var _this = this; 37025 this.dom.titleRow 37026 .removeClass(this.classes.hide) 37027 .detach() 37028 .append(this.dom.title); 37029 // If the clear button is permitted attach it 37030 if (this.c.clear) { 37031 this.dom.clearAll 37032 .appendTo(this.dom.titleRow) 37033 .on('click.dtsps', function () { return _this.clearSelections(); }); 37034 } 37035 if (this.c.collapse) { 37036 this.dom.showAll.appendTo(this.dom.titleRow); 37037 this.dom.collapseAll.appendTo(this.dom.titleRow); 37038 this._setCollapseListener(); 37039 } 37040 // Attach the container for each individual pane to the overall container 37041 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37042 var pane = _a[_i]; 37043 this.dom.panes.append(pane.dom.container); 37044 } 37045 // Attach everything to the document 37046 this.dom.container 37047 .text('') 37048 .removeClass(this.classes.hide) 37049 .append(this.dom.titleRow) 37050 .append(this.dom.panes); 37051 // WORKAROUND
37052 this.s.panes.forEach(function (pane) { return pane.setListeners(); }); 37053 if ($$1('div.' + this.classes.container).length === 0) { 37054 this.dom.container.prependTo(this.s.dt); 37055 } 37056 }; 37057 /** 37058 * If there are no panes to display then this method is called to either 37059 * display a message in their place or hide them completely. 37060 */ 37061 SearchPanes.prototype._attachMessage = function () { 37062 // Create a message to display on the screen 37063 var message; 37064 try { 37065 message = this.s.dt.i18n('searchPanes.emptyPanes', this.c.i18n.emptyPanes); 37066 } 37067 catch (error) { 37068 message = null; 37069 } 37070 // If the message is an empty string then searchPanes.emptyPanes is undefined, 37071 // therefore the pane container should be removed from the display 37072 if (message === null) { 37073 this.dom.container.addClass(this.classes.hide); 37074 this.dom.titleRow.removeClass(this.classes.hide); 37075 return; 37076 } 37077 // Otherwise display the message 37078 this.dom.container.removeClass(this.classes.hide); 37079 this.dom.titleRow.addClass(this.classes.hide); 37080 this.dom.emptyMessage.html(message).appendTo(this.dom.container); 37081 }; 37082 /** 37083 * Attaches the panes to the document and displays a message or hides if there are none 37084 */ 37085 SearchPanes.prototype._attachPaneContainer = function () { 37086 // If a pane is to be displayed then attach the normal pane output 37087 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37088 var pane = _a[_i]; 37089 if (pane.s.displayed === true) { 37090 this._attach(); 37091 return; 37092 } 37093 } 37094 // Otherwise attach the custom message or remove the container from the display 37095 this._attachMessage(); 37096 }; 37097 /** 37098 * Checks which panes are collapsed and then performs relevant actions to the collapse/show all buttons 37099 */ 37100 SearchPanes.prototype._checkCollapse = function () { 37101 var disableClose = true; 37102 var disableShow = true; 37103 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37104 var pane = _a[_i]; 37105 if (pane.s.displayed) { 37106 // If the pane is not collapsed 37107 if (!pane.dom.collapseButton.hasClass(pane.classes.rotated)) { 37108 // Enable the collapse all button 37109 this.dom.collapseAll.removeClass(this.classes.disabledButton).removeAttr('disabled'); 37110 disableClose = false; 37111 } 37112 else { 37113 // Otherwise enable the show all button 37114 this.dom.showAll.removeClass(this.classes.disabledButton).removeAttr('disabled'); 37115 disableShow = false; 37116 } 37117 } 37118 } 37119 // If this flag is still true, no panes are open so the close button should be disabled 37120 if (disableClose) { 37121 this.dom.collapseAll.addClass(this.classes.disabledButton).attr('disabled', 'true'); 37122 } 37123 // If this flag is still true, no panes are closed so the show button should be disabled 37124 if (disableShow) { 37125 this.dom.showAll.addClass(this.classes.disabledButton).attr('disabled', 'true'); 37126 } 37127 }; 37128 /** 37129 * Attaches the message to the document but does not add any panes 37130 */ 37131 SearchPanes.prototype._checkMessage = function () { 37132 // If a pane is to be displayed then attach the normal pane output 37133 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37134 var pane = _a[_i]; 37135 if (pane.s.displayed === true) { 37136 // Ensure that the empty message is removed if a pane is displayed 37137 this.dom.emptyMessage.detach(); 37138 this.dom.titleRow.removeClass(this.classes.hide); 37139 return; 37140 } 37141 } 37142 // Otherwise attach the custom message or remove the container from the display 37143 this._attachMessage(); 37144 }; 37145 /** 37146 * Collapses all of the panes 37147 */ 37148 SearchPanes.prototype._collapseAll = function () { 37149 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37150 var pane = _a[_i]; 37151 pane.collapse(); 37152 } 37153 }; 37154 /** 37155 * Finds a pane based upon the name of that pane 37156 * 37157 * @param name string representing the name of the pane 37158 * @returns SearchPane The pane which has that name 37159 */ 37160 SearchPanes.prototype._findPane = function (name) { 37161 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37162 var pane = _a[_i]; 37163 if (name === pane.s.name) { 37164 return pane; 37165 } 37166 } 37167 }; 37168 /** 37169 * Gets the selection list from the previous state and stores it in the selectionList Property 37170 */ 37171 SearchPanes.prototype._getState = function () { 37172 var loadedFilter = this.s.dt.state.loaded(); 37173 if (loadedFilter && loadedFilter.searchPanes && loadedFilter.searchPanes.selectionList) { 37174 this.s.selectionList = loadedFilter.searchPanes.selectionList; 37175 } 37176 }; 37177 SearchPanes.prototype._makeSelections = function (selectList) { 37178 for (var _i = 0, selectList_1 = selectList; _i < selectList_1.length; _i++) { 37179 var selection = selectList_1[_i]; 37180 var pane = void 0; 37181 for (var _a = 0, _b = this.s.panes; _a < _b.length; _a++) { 37182 var p = _b[_a]; 37183 if (p.s.index === selection.column) { 37184 pane = p; 37185 break; 37186 } 37187 } 37188 if (pane && pane.s.dtPane) { 37189 for (var j = 0; j < pane.s.dtPane.rows().data().toArray().length; j++) { 37190 if (selection.rows.includes(typeof pane.s.dtPane.row(j).data().filter === 'function' ? 37191 pane.s.dtPane.cell(j, 0).data() : 37192 pane.s.dtPane.row(j).data().filter)) { 37193 pane.s.dtPane.row(j).select(); 37194 } 37195 } 37196 pane.updateTable(); 37197 } 37198 } 37199 }; 37200 /** 37201 * Declares the instances of individual searchpanes depen
37201dant on the number of columns. 37202 * It is necessary to run this once preInit has completed otherwise no panes will be 37203 * created as the column count will be 0. 37204 * 37205 * @param table the DataTable api for the parent table 37206 * @param paneSettings the settings passed into the constructor 37207 * @param opts the options passed into the constructor 37208 */ 37209 SearchPanes.prototype._paneDeclare = function (table, paneSettings, opts) { 37210 var _this = this; 37211 // Create Panes 37212 table 37213 .columns(this.c.columns.length > 0 ? this.c.columns : undefined) 37214 .eq(0) 37215 .each(function (idx) { 37216 _this.s.panes.push(new _this.s.paneClass(paneSettings, opts, idx, _this.dom.panes)); 37217 }); 37218 // If there is any extra custom panes defined then create panes for them too 37219 var colCount = table.columns().eq(0).toArray().length; 37220 for (var i = 0; i < this.c.panes.length; i++) { 37221 var id = colCount + i; 37222 this.s.panes.push(new this.s.paneClass(paneSettings, opts, id, this.dom.panes, this.c.panes[i])); 37223 } 37224 // If a custom ordering is being used 37225 if (this.c.order.length > 0) { 37226 // Make a new Array of panes based upon the order 37227 this.s.panes = this.c.order.map(function (name) { return _this._findPane(name); }); 37228 } 37229 // If this internal property is true then the DataTable has been initialised already 37230 if (this.s.dt.settings()[0]._bInitComplete) { 37231 this._startup(table); 37232 } 37233 else { 37234 // Otherwise add the paneStartup function to the list of functions 37235 // that are to be run when the table is initialised. This will garauntee that the 37236 // panes are initialised before the init event and init Complete callback is fired 37237 this.s.dt.settings()[0].aoInitComplete.push({ 37238 fn: function () { return _this._startup(table); } 37239 }); 37240 } 37241 }; 37242 /** 37243 * Sets the listeners for the collapse and show all buttons 37244 * Also sets and performs checks on current panes to see if they are collapsed 37245 */ 37246 SearchPanes.prototype._setCollapseListener = function () { 37247 var _this = this; 37248 this.dom.collapseAll.on('click.dtsps', function () { 37249 _this._collapseAll(); 37250 _this.dom.collapseAll.addClass(_this.classes.disabledButton).attr('disabled', 'true'); 37251 _this.dom.showAll.removeClass(_this.classes.disabledButton).removeAttr('disabled'); 37252 _this.s.dt.state.save(); 37253 }); 37254 this.dom.showAll.on('click.dtsps', function () { 37255 _this._showAll(); 37256 _this.dom.showAll.addClass(_this.classes.disabledButton).attr('disabled', 'true'); 37257 _this.dom.collapseAll.removeClass(_this.classes.disabledButton).removeAttr('disabled'); 37258 _this.s.dt.state.save(); 37259 }); 37260 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37261 var pane = _a[_i]; 37262 // We want to make the same check whenever there is a collapse/expand 37263 pane.dom.topRow.on('collapse.dtsps', function () { return _this._checkCollapse(); }); 37264 } 37265 this._checkCollapse(); 37266 }; 37267 /** 37268 * Shows all of the panes 37269 */ 37270 SearchPanes.prototype._showAll = function () { 37271 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37272 var pane = _a[_i]; 37273 pane.show(); 37274 } 37275 }; 37276 /** 37277 * Initialises the tables previous/preset selections and initialises callbacks for events 37278 * 37279 * @param table the parent table for which the searchPanes are being created 37280 */ 37281 SearchPanes.prototype._startup = function (table) { 37282 var _this = this; 37283 // Attach clear button and title bar to the document 37284 this._attach(); 37285 this.dom.panes.empty(); 37286 var hostSettings = this.s.dt.settings()[0]; 37287 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37288 var pane = _a[_i]; 37289 pane.rebuildPane(Object.keys(this.s.serverData).length > 0 ? this.s.serverData : undefined); 37290 this.dom.panes.append(pane.dom.container); 37291 } 37292 // If the layout is set to auto then the panes need to be resized to their best fit 37293 if (this.c.layout === 'auto') { 37294 this.resizePanes(); 37295 } 37296 var loadedFilter = this.s.dt.state.loaded(); 37297 // Reset the paging if that has been saved in the state 37298 if (!this.s.stateRead && loadedFilter) { 37299 this.s.dt 37300 .page(loadedFilter.start / this.s.dt.page.len()) 37301 .draw('page'); 37302 } 37303 this.s.stateRead = true; 37304 this._checkMessage(); 37305 // When a draw is called on the DataTable, update all of the panes incase the data in the DataTable has changed 37306 table.on('preDraw.dtsps', function () { 37307 // Check that the panes are not updating to avoid infinite loops 37308 // Also check that this draw is not due to paging 37309 if (!_this.s.updating && !_this.s.paging) { 37310 _this._updateFilterCount(); 37311 _this._updateSelection(); 37312 } 37313 // Paging flag reset - we only need to dodge the draw once 37314 _this.s.paging = false; 37315 }); 37316 $$1(window).on('resize.dtsps', dataTable$1.util.throttle(function () { return _this.resizePanes(); })); 37317 // Whenever a state save occurs store the selection list in the state object 37318 this.s.dt.on('stateSaveParams.dtsps', function (e, settings, data) { 37319 if (settings !== hostSettings) { 37320 return; 37321 } 37322 if (data.searchPanes === undefined) { 37323 data.searchPanes = {}; 37324 } 37325 data.searchPanes.selectionList = _this.s.selectionList; 37326 }); 37327 this._stateLoadListener(); 37328 // Listener for paging on main table 37329 table.off('page.dtsps page-nc.dtsps').on('page.dtsps page-nc.dtsps', function (e, s) { 37330 _this.s.paging = true; 37331 // This is an indicator to any selection tracking classes that paging has occured 37332 // It has to happen here so that we don't stack event listeners unnecessarily
37333 // The value is only ever set back to false in the SearchPanesST class 37334 // Equally it is never read in this class 37335 _this.s.pagingST = true; 37336 _this.s.page = _this.s.dt.page(); 37337 }); 37338 if (this.s.dt.page.info().serverSide) { 37339 table.off('preXhr.dtsps').on('preXhr.dtsps', function (e, settings, data) { 37340 if (settings !== hostSettings) { 37341 return; 37342 } 37343 if (!data.searchPanes) { 37344 data.searchPanes = {}; 37345 } 37346 if (!data.searchPanes_null) { 37347 data.searchPanes_null = {}; 37348 } 37349 // Count how many filters are being applied 37350 var filterCount = 0; 37351 for (var _i = 0, _a = _this.s.panes; _i < _a.length; _i++) { 37352 var pane = _a[_i]; 37353 var src = _this.s.dt.column(pane.s.index).dataSrc(); 37354 if (!data.searchPanes[src]) { 37355 data.searchPanes[src] = {}; 37356 } 37357 if (!data.searchPanes_null[src]) { 37358 data.searchPanes_null[src] = {}; 37359 } 37360 if (pane.s.dtPane) { 37361 var rowData = pane.s.dtPane.rows({ selected: true }).data().toArray(); 37362 for (var i = 0; i < rowData.length; i++) { 37363 data.searchPanes[src][i] = rowData[i].filter; 37364 if (!data.searchPanes[src][i]) { 37365 data.searchPanes_null[src][i] = true; 37366 } 37367 filterCount++; 37368 } 37369 } 37370 } 37371 // If there is a filter to be applied, then we need to read from the start of the result set 37372 // and set the paging to 0. This matches the behaviour of client side processing 37373 if (filterCount > 0) { 37374 // If the number of filters has changed we need to read from the start of the 37375 // result set and reset the paging 37376 if (filterCount !== _this.s.filterCount) { 37377 data.start = 0; 37378 _this.s.page = 0; 37379 } 37380 // Otherwise it is a paging request and we need to read from whatever the paging has been set to 37381 else { 37382 data.start = _this.s.page * _this.s.dt.page.len(); 37383 } 37384 _this.s.dt.page(_this.s.page); 37385 _this.s.filterCount = filterCount; 37386 } 37387 if (_this.s.selectionList.length > 0) { 37388 data.searchPanesLast = _this.s.dt 37389 .column(_this.s.selectionList[_this.s.selectionList.length - 1].column) 37390 .dataSrc(); 37391 } 37392 // Config options that will change how the querying is done 37393 data.searchPanes_options = { 37394 cascade: _this.c.cascadePanes, 37395 viewCount: _this.c.viewCount, 37396 viewTotal: _this.c.viewTotal 37397 }; 37398 }); 37399 } 37400 else { 37401 table.on('preXhr.dtsps', function () { return _this.s.panes.forEach(function (pane) { return pane.clearData(); }); }); 37402 } 37403 // If the data is reloaded from the server then it is possible that it has changed completely, 37404 // so we need to rebuild the panes 37405 this.s.dt.on('xhr.dtsps', function (e, settings) { 37406 if (settings.nTable !== _this.s.dt.table().node()) { 37407 return; 37408 } 37409 if (!_this.s.dt.page.info().serverSide) { 37410 var processing_1 = false; 37411 _this.s.dt.one('preDraw.dtsps', function () { 37412 if (processing_1) { 37413 return; 37414 } 37415 var page = _this.s.dt.page(); 37416 processing_1 = true; 37417 _this.s.updating = true; 37418 _this.dom.panes.empty(); 37419 for (var _i = 0, _a = _this.s.panes; _i < _a.length; _i++) { 37420 var pane = _a[_i]; 37421 pane.clearData(); // Clears all of the bins and will mean that the data has to be re-read 37422 // Pass a boolean to say whether this is the last choice made for maintaining selections 37423 // when rebuilding 37424 pane.rebuildPane(undefined, true); 37425 _this.dom.panes.append(pane.dom.container); 37426 } 37427 if (!_this.s.dt.page.info().serverSide) { 37428 _this.s.dt.draw(); 37429 } 37430 _this.s.updating = false; 37431 _this._updateSelection(); 37432 _this._checkMessage(); 37433 _this.s.dt.one('draw.dtsps', function () { 37434 _this.s.updating = true; 37435 _this.s.dt.page(page).draw(false); 37436 _this.s.updating = false; 37437 }); 37438 }); 37439 } 37440 }); 37441 // PreSelect any selections which have been defined using the preSelect option 37442 var selectList = this.c.preSelect; 37443 if (loadedFilter && loadedFilter.searchPanes && loadedFilter.searchPanes.selectionList) { 37444 selectList = loadedFilter.searchPanes.selectionList; 37445 } 37446 this._makeSelections(selectList); 37447 // Update the title bar to show how many filters have been selected 37448 this._updateFilterCount(); 37449 // If the table is destroyed and restarted then clear the selections so that they do not persist. 37450 table.on('destroy.dtsps', function (e, settings) { 37451 if (settings !== hostSettings) { 37452 return; 37453 } 37454 for (var _i = 0, _a = _this.s.panes; _i < _a.length; _i++) { 37455 var pane = _a[_i]; 37456 pane.destroy(); 37457 } 37458 table.off('.dtsps'); 37459 _this.dom.showAll.off('.dtsps'); 37460 _this.dom.clearAll.off('.dtsps'); 37461 _this.dom.collapseAll.off('.dtsps'); 37462 $$1(table.table().node()).off('.dtsps'); 37463 _this.dom.container.detach(); 37464 _this.clearSelections(); 37465 }); 37466 if (this.c.collapse) { 37467 this._setCollapseListener(); 37468 } 37469 // When the clear All button has been pressed clear all of the selections in the panes 37470 if (this.c.clear) { 37471 this.dom.clearAll.on('click.dtsps', function () { return _this.clearSelections(); }); 37472 } 37473 hostSettings._searchPanes = this; 37474 // This state save is required so that state is maintained over multiple refreshes if no actions are made 37475 this.s.dt.state.save(); 37476 }; 37477 /** 37478 * Updates the number of filters that have been applied in the title 37479 */ 37480 SearchPanes.prototype._updateFilterCount = function () { 37481 var filterCount = 0; 37482 // Add the number of all of the filters throughout the panes 37483 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37484 var pane = _a[_i]; 37485 if (pane.s.dtPane) { 37486 filterCount += pane.getPaneCount(); 37487 } 37488 } 37489 // Run the message through the internationalisation method to improve readability 37490 this.dom.title.html(this.s.dt.i18n('searchPanes.title', this.c.i18n.title, filterCount)); 37491 if (this.c.filterChanged && typeof this.c.filterChanged === 'function') { 37492 this.c.filterChanged.call(this.s.dt, filterCount); 37493 } 37494 if (filterCount === 0) { 37495 this.dom.clearAll.addClass(this.classes.disabledButton).attr('disabled', 'true'); 37496 } 37497 else { 37498 this.dom.clearAll.removeClass(this.classes.disabledButton).removeAttr('disabled'); 37499 } 37500 }; 37501 SearchPanes.version = '2.1.2'; 37502 SearchPanes.classes = { 37503 clear: 'dtsp-clear', 37504 clearAll: 'dtsp-clearAll', 37505 collapseAll: 'dtsp-collapseAll', 37506 container: 'dtsp-searchPanes', 37507 disabledButton: 'dtsp-disabledButton', 37508 emptyMessage: 'dtsp-emptyMessage', 37509 hide: 'dtsp-hidden', 37510 panes: 'dtsp-panesContainer', 37511 search: 'dtsp-search', 37512 showAll: 'dtsp-showAll', 37513 title: 'dtsp-title', 37514 titleRow: 'dtsp-titleRow' 37515 }; 37516 // Define SearchPanes default options 37517 SearchPanes.defaults = { 37518 cascadePanes: false, 37519 clear: true, 37520 collapse: true, 37521 columns: [], 37522 container: function (dt) { 37523 return dt.table().container(); 37524 }, 37525 filterChanged: undefined, 37526 i18n: { 37527 clearMessage: 'Clear All', 37528 clearPane: '×', 37529 collapse: { 37530 0: 'SearchPanes', 37531 _: 'SearchPanes (%d)' 37532 }, 37533 collapseMessage: 'Collapse All', 37534 count: '{total}', 37535 emptyMessage: '<em>No data</em>', 37536 emptyPanes: 'No SearchPanes', 37537 loadMessage: 'Loading Search Panes...', 37538 showMessage: 'Show All', 37539 title: 'Filters Active - %d' 37540 }, 37541 layout: 'auto', 37542 order: [], 37543 panes: [], 37544 preSelect: [], 37545 viewCount: true, 37546 viewTotal: false 37547 }; 37548 return SearchPanes; 37549 }()); 37550 37551 var __extends = (window && window.__extends) || (function () { 37552 var extendStatics = function (d, b) { 37553 extendStatics = Object.setPrototypeOf || 37554 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 37555 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 37556 return extendStatics(d, b); 37557 }; 37558 return function (d, b) { 37559 extendStatics(d, b); 37560 function __() { this.constructor = d; } 37561 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 37562 }; 37563 })(); 37564 var SearchPanesST = /** @class */ (function (_super) { 37565 __extends(SearchPanesST, _super); 37566 function SearchPanesST(paneSettings, opts, fromPreInit) { 37567 if (fromPreInit === void 0) { fromPreInit = false; } 37568 var _this = this; 37569 var paneClass; 37570 if (opts.cascadePanes && opts.viewTotal) { 37571 paneClass = SearchPaneCascadeViewTotal; 37572 } 37573 else if (opts.cascadePanes) { 37574 paneClass = SearchPaneCascade; 37575 } 37576 else if (opts.viewTotal) { 37577 paneClass = SearchPaneViewTotal; 37578 } 37579 _this = _super.call(this, paneSettings, opts, fromPreInit, paneClass) || this; 37580 var dt = _this.s.dt; 37581 var loadedFilter = dt.state.loaded(); 37582 var loadFn = function () { return _this._initSelectionListeners(true, loadedFilter && loadedFilter.searchPanes && loadedFilter.searchPanes.selectionList ? 37583 loadedFilter.searchPanes.selectionList : 37584 _this.c.preSelect); }; 37585 if (dt.settings()[0]._bInitComplete) { 37586 loadFn(); 37587 } 37588 else { 37589 dt.off('init.dtsps').on('init.dtsps', loadFn); 37590 } 37591 return _this; 37592 } 37593 /** 37594 * Ensures that the correct selection listeners are set for selection tracking 37595 * 37596 * @param preSelect Any values that are to be preselected 37597 */ 37598 SearchPanesST.prototype._initSelectionListeners = function (isPreselect, preSelect) { 37599 if (isPreselect === void 0) { isPreselect = true; } 37600 if (preSelect === void 0) { preSelect = []; } 37601 if (isPreselect) { 37602 this.s.selectionList = preSelect; 37603 } 37604 // Set selection listeners for each pane 37605 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37606 var pane = _a[_i]; 37607 if (pane.s.displayed) { 37608 pane.s.dtPane 37609 .off('select.dtsp') 37610 .on('select.dtsp', this._update(pane)) 37611 .off('deselect.dtsp') 37612 .on('deselect.dtsp', this._updateTimeout(pane)); 37613 } 37614 } 37615 // Update on every draw 37616 this.s.dt.off('draw.dtsps').on('draw.dtsps', this._update()); 37617 // Also update right now as table has just initialised 37618 this._updateSelectionList(); 37619 }; 37620 /** 37621 * Retrieve the total values from the server data 37622 */ 37623 SearchPanesST.prototype._serverTotals = function () { 37624 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37625 var pane = _a[_i]; 37626 if (pane.s.colOpts.show) { 37627 var colTitle = this.s.dt.column(pane.s.index).dataSrc(); 37628 var blockVT = true; 37629 // If any of the counts are not equal to the totals filtering must be active 37630 if (this.s.serverData.searchPanes.options[colTitle]) { 37631 for (var _b = 0, _c = this.s.serverData.searchPanes.options[colTitle]; _b < _c.length; _b++) { 37632 var data = _c[_b];
37633 if (data.total !== data.count) { 37634 blockVT = false; 37635 break; 37636 } 37637 } 37638 } 37639 // Set if filtering is present on the pane and populate the data arrays 37640 pane.s.filteringActive = !blockVT; 37641 pane._serverPopulate(this.s.serverData); 37642 } 37643 } 37644 }; 37645 /** 37646 * Set's the function that is to be performed when a state is loaded 37647 * 37648 * Overrides the method in SearchPanes 37649 */ 37650 SearchPanesST.prototype._stateLoadListener = function () { 37651 var _this = this; 37652 var stateLoadFunction = function (e, settings, data) { 37653 if (data.searchPanes === undefined) { 37654 return; 37655 } 37656 // Set the selection list for the panes so that the correct 37657 // rows can be reselected and in the right order 37658 _this.s.selectionList = 37659 data.searchPanes.selectionList ? 37660 data.searchPanes.selectionList : 37661 []; 37662 // Find the panes that match from the state and the actual instance 37663 if (data.searchPanes.panes) { 37664 for (var _i = 0, _a = data.searchPanes.panes; _i < _a.length; _i++) { 37665 var loadedPane = _a[_i]; 37666 for (var _b = 0, _c = _this.s.panes; _b < _c.length; _b++) { 37667 var pane = _c[_b]; 37668 if (loadedPane.id === pane.s.index && pane.s.dtPane) { 37669 // Set the value of the searchbox 37670 pane.dom.searchBox.val(loadedPane.searchTerm); 37671 // Set the value of the order 37672 pane.s.dtPane.order(loadedPane.order); 37673 } 37674 } 37675 } 37676 } 37677 _this._updateSelectionList(); 37678 }; 37679 this.s.dt.off('stateLoadParams.dtsps', stateLoadFunction).on('stateLoadParams.dtsps', stateLoadFunction); 37680 }; 37681 /** 37682 * Remove the function's actions when using cascade 37683 * 37684 * Overrides the method in SearchPanes 37685 */ 37686 SearchPanesST.prototype._updateSelection = function () { 37687 return; 37688 }; 37689 /** 37690 * Returns a function that updates the selection list based on a specific pane 37691 * Also clears the timeout to stop the deselect from running 37692 * 37693 * @param pane the pane that is to have it's selections loaded 37694 */ 37695 SearchPanesST.prototype._update = function (pane) { 37696 var _this = this; 37697 if (pane === void 0) { pane = undefined; } 37698 return function () { 37699 if (pane) { 37700 clearTimeout(pane.s.deselectTimeout); 37701 } 37702 _this._updateSelectionList(pane); 37703 }; 37704 }; 37705 /** 37706 * Returns a function that updates the selection list based on a specific pane 37707 * Also sets a timeout incase a select is about to be made 37708 * 37709 * @param pane the pane that is to have it's selections loaded 37710 */ 37711 SearchPanesST.prototype._updateTimeout = function (pane) { 37712 var _this = this; 37713 if (pane === void 0) { pane = undefined; } 37714 return function () { return pane ? 37715 pane.s.deselectTimeout = setTimeout(function () { return _this._updateSelectionList(pane); }, 50) : 37716 _this._updateSelectionList(); }; 37717 }; 37718 /** 37719 * Updates the selection list to include the latest selections for a given pane 37720 * 37721 * @param index The index of the pane that is to be updated 37722 * @param selected Which rows are selected within the pane 37723 */ 37724 SearchPanesST.prototype._updateSelectionList = function (paneIn) {
37725 if (paneIn === void 0) { paneIn = undefined; } 37726 // Bail if any of these flags are set 37727 if (this.s.pagingST) { 37728 // Reset pagingST flag 37729 this.s.pagingST = false; 37730 return; 37731 } 37732 else if (this.s.updating || paneIn && paneIn.s.serverSelecting) { 37733 return; 37734 } 37735 if (paneIn !== undefined) { 37736 if (this.s.dt.page.info().serverSide) { 37737 paneIn._updateSelection(); 37738 } 37739 // Get filter values for all of the rows and the selections 37740 var rows = paneIn.s.dtPane.rows({ selected: true }).data().toArray().map(function (el) { return el.filter; }); 37741 this.s.selectionList = this.s.selectionList.filter(function (selection) { return selection.column !== paneIn.s.index; }); 37742 if (rows.length > 0) { 37743 this.s.selectionList.push({ 37744 column: paneIn.s.index, 37745 rows: rows 37746 }); 37747 paneIn.dom.clear.removeClass(this.classes.disabledButton).removeAttr('disabled'); 37748 } 37749 else { 37750 paneIn.dom.clear.addClass(this.classes.disabledButton).attr('disabled', 'true'); 37751 } 37752 if (this.s.dt.page.info().serverSide) { 37753 this.s.dt.draw(false); 37754 } 37755 } 37756 this._remakeSelections(); 37757 this._updateFilterCount(); 37758 }; 37759 /** 37760 * Remake the selections that were present before new data or calculations have occured 37761 */ 37762 SearchPanesST.prototype._remakeSelections = function () { 37763 this.s.updating = true; 37764 if (!this.s.dt.page.info().serverSide) { 37765 var tmpSL = this.s.selectionList; 37766 var anotherFilter = false; 37767 this.clearSelections(); 37768 this.s.dt.draw(false); 37769 // When there are no selections present if the length of the data does not match the searched data 37770 // then another filter is present 37771 if (this.s.dt.rows().toArray()[0].length > this.s.dt.rows({ search: 'applied' }).toArray()[0].length) { 37772 anotherFilter = true; 37773 } 37774 this.s.selectionList = tmpSL; 37775 // Update the rows in each pane 37776 for (var _i = 0, _a = this.s.panes; _i < _a.length; _i++) { 37777 var pane = _a[_i]; 37778 if (pane.s.displayed) { 37779 pane.s.filteringActive = anotherFilter; 37780 pane.updateRows(); 37781 } 37782 } 37783 for (var _b = 0, _c = this.s.selectionList; _b < _c.length; _b++) { 37784 var selection = _c[_b]; 37785 var pane = void 0; 37786 for (var _d = 0, _e = this.s.panes; _d < _e.length; _d++) { 37787 var paneCheck = _e[_d]; 37788 if (paneCheck.s.index === selection.column) { 37789 pane = paneCheck; 37790 break; 37791 } 37792 } 37793 if (!pane.s.dtPane) { 37794 continue; 37795 } 37796 var ids = pane.s.dtPane.rows().indexes().toArray(); 37797 // Select the rows that are present in the selection list 37798 for (var i = 0; i < selection.rows.length; i++) { 37799 var rowFound = false; 37800 for (var _f = 0, ids_1 = ids; _f < ids_1.length; _f++) { 37801 var id = ids_1[_f]; 37802 var currRow = pane.s.dtPane.row(id); 37803 var data = currRow.data(); 37804 if (selection.rows[i] === data.filter) { 37805 currRow.select(); 37806 rowFound = true; 37807 } 37808 } 37809 if (!rowFound) { 37810 selection.rows.splice(i, 1); 37811 i--; 37812 } 37813 } 37814 pane.s.selections = selection.rows; 37815 // If there are no rows selected then don't bother continuing past here
37816 // Will just increase processing time and skew the rows that are shown in the table 37817 if (selection.rows.length === 0) { 37818 continue; 37819 } 37820 // Update the table to display the current results 37821 this.s.dt.draw(false); 37822 var filteringActive = false; 37823 var filterCount = 0; 37824 var prevSelectedPanes = 0; 37825 var selectedPanes = 0; 37826 // Add the number of all of the filters throughout the panes 37827 for (var _g = 0, _h = this.s.panes; _g < _h.length; _g++) { 37828 var currPane = _h[_g]; 37829 if (currPane.s.dtPane) { 37830 filterCount += currPane.getPaneCount(); 37831 if (filterCount > prevSelectedPanes) { 37832 selectedPanes++; 37833 prevSelectedPanes = filterCount; 37834 } 37835 } 37836 } 37837 filteringActive = filterCount > 0; 37838 for (var _j = 0, _k = this.s.panes; _j < _k.length; _j++) { 37839 var currPane = _k[_j]; 37840 if (currPane.s.displayed) { 37841 // Set the filtering active flag 37842 if (anotherFilter || pane.s.index !== currPane.s.index || !filteringActive) { 37843 currPane.s.filteringActive = filteringActive || anotherFilter; 37844 } 37845 else if (selectedPanes === 1) { 37846 currPane.s.filteringActive = false; 37847 } 37848 // Update the rows to show correct counts 37849 if (currPane.s.index !== pane.s.index) { 37850 currPane.updateRows(); 37851 } 37852 } 37853 } 37854 } 37855 // Update table to show final search results 37856 this.s.dt.draw(false); 37857 } 37858 else { 37859 // Identify the last pane to have a change in selection 37860 var pane = void 0; 37861 if (this.s.selectionList.length > 0) { 37862 pane = this.s.panes[this.s.selectionList[this.s.selectionList.length - 1].column]; 37863 } 37864 // Update the rows of all of the other panes 37865 for (var _l = 0, _m = this.s.panes; _l < _m.length; _l++) { 37866 var currPane = _m[_l]; 37867 if (currPane.s.displayed && (!pane || currPane.s.index !== pane.s.index)) { 37868 currPane.updateRows(); 37869 } 37870 } 37871 } 37872 this.s.updating = false; 37873 }; 37874 return SearchPanesST; 37875 }(SearchPanes)); 37876 37877 /*! SearchPanes 2.1.2 37878 * © SpryMedia Ltd - datatables.net/license 37879 */ 37880 setJQuery$4($); 37881 setJQuery($); 37882 setJQuery$3($); 37883 setJQuery$2($); 37884 setJQuery$1($); 37885 var dataTable = $.fn.dataTable; 37886 // eslint-disable-next-line no-extra-parens 37887 dataTable.SearchPanes = SearchPanes; 37888 // eslint-disable-next-line no-extra-parens 37889 DataTable.SearchPanes = SearchPanes; 37890 // eslint-disable-next-line no-extra-parens 37891 dataTable.SearchPanesST = SearchPanesST; 37892 // eslint-disable-next-line no-extra-parens 37893 DataTable.SearchPanesST = SearchPanesST; 37894 // eslint-disable-next-line no-extra-parens 37895 dataTable.SearchPane = SearchPane; 37896 // eslint-disable-next-line no-extra-parens 37897 DataTable.SearchPane = SearchPane; 37898 // eslint-disable-next-line no-extra-parens 37899 dataTable.SearchPaneViewTotal = SearchPaneViewTotal; 37900 // eslint-disable-next-line no-extra-parens 37901 DataTable.SearchPaneViewTotal = SearchPaneViewTotal; 37902 // eslint-disable-next-line no-extra-parens 37903 dataTable.SearchPaneCascade = SearchPaneCascade; 37904 // eslint-disable-next-line no-extra-parens 37905 DataTable.SearchPaneCascade = SearchPaneCascade; 37906 // eslint-disable-next-line no-extra-parens 37907 dataTable.SearchPaneCascadeViewTotal = SearchPaneCascadeViewTotal; 37908 // eslint-disable-next-line no-extra-parens 37909 DataTable.SearchPaneCascadeViewTotal = SearchPaneCascadeViewTotal; 37910 // eslint-disable-next-line no-extra-parens 37911 var apiRegister = $.fn.dataTable.Api.register; 37912 apiRegister('searchPanes()', function () { 37913 return this; 37914 });
37915 apiRegister('searchPanes.clearSelections()', function () { 37916 return this.iterator('table', function (ctx) { 37917 if (ctx._searchPanes) { 37918 ctx._searchPanes.clearSelections(); 37919 } 37920 }); 37921 }); 37922 apiRegister('searchPanes.rebuildPane()', function (targetIdx, maintainSelections) { 37923 return this.iterator('table', function (ctx) { 37924 if (ctx._searchPanes) { 37925 ctx._searchPanes.rebuild(targetIdx, maintainSelections); 37926 } 37927 }); 37928 }); 37929 apiRegister('searchPanes.resizePanes()', function () { 37930 var ctx = this.context[0]; 37931 return ctx._searchPanes ? 37932 ctx._searchPanes.resizePanes() : 37933 null; 37934 }); 37935 apiRegister('searchPanes.container()', function () { 37936 var ctx = this.context[0]; 37937 return ctx._searchPanes 37938 ? ctx._searchPanes.getNode() 37939 : null; 37940 }); 37941 DataTable.ext.buttons.searchPanesClear = { 37942 action: function (e, dt) { 37943 dt.searchPanes.clearSelections(); 37944 }, 37945 text: 'Clear Panes' 37946 }; 37947 DataTable.ext.buttons.searchPanes = { 37948 action: function (e, dt, node, config) { 37949 var _this = this; 37950 if (!config._panes) { 37951 // No SearchPanes on this button yet - initialise and show 37952 this.processing(true); 37953 setTimeout(function () { 37954 _buttonSourced(dt, node, config); 37955 _this.popover(config._panes.getNode(), { 37956 align: 'container', 37957 span: 'container' 37958 }); 37959 config._panes.rebuild(undefined, true); 37960 _this.processing(false); 37961 }, 10); 37962 } 37963 else { 37964 // Already got SP - show it 37965 this.popover(config._panes.getNode(), { 37966 align: 'container', 37967 span: 'container' 37968 }); 37969 config._panes.rebuild(undefined, true); 37970 } 37971 }, 37972 init: function (dt, node, config) { 37973 dt.button(node).text(config.text || dt.i18n('searchPanes.collapse', 'SearchPanes', 0)); 37974 // If state save is enabled, we need to initialise SP immediately 37975 // to allow any saved state to be restored. Otherwise we can delay 37976 // the init until needed by button press 37977 if (dt.init().stateSave) { 37978 _buttonSourced(dt, node, config); 37979 } 37980 }, 37981 config: {}, 37982 text: '' 37983 }; 37984 function _buttonSourced(dt, node, config) { 37985 var buttonOpts = $.extend({ 37986 filterChanged: function (count) { 37987 dt.button(node).text(dt.i18n('searchPanes.collapse', dt.context[0].oLanguage.searchPanes !== undefined ? 37988 dt.context[0].oLanguage.searchPanes.collapse : 37989 dt.context[0]._searchPanes.c.i18n.collapse, count)); 37990 } 37991 }, config.config); 37992 var panes = buttonOpts && (buttonOpts.cascadePanes || buttonOpts.viewTotal) ? 37993 new DataTable.SearchPanesST(dt, buttonOpts) : 37994 new DataTable.SearchPanes(dt, buttonOpts); 37995 dt.button(node).text(config.text || dt.i18n('searchPanes.collapse', panes.c.i18n.collapse, 0)); 37996 config._panes = panes; 37997 } 37998 function _init(settings, options, fromPre) { 37999 if (options === void 0) { options = null; } 38000 if (fromPre === void 0) { fromPre = false; } 38001 var api = new dataTable.Api(settings); 38002 var opts = options 38003 ? options 38004 : api.init().searchPanes || dataTable.defaults.searchPanes; 38005 var searchPanes = opts && (opts.cascadePanes || opts.viewTotal) ? 38006 new SearchPanesST(api, opts, fromPre) : 38007 new SearchPanes(api, opts, fromPre); 38008 var node = searchPanes.getNode(); 38009 return node; 38010 } 38011 // Attach a listener to the document which listens for DataTables initialisation 38012 // events so we can automatically initialise 38013 $(document).on('preInit.dt.dtsp', function (e, settings) { 38014 if (e.namespace !== 'dt') { 38015 return; 38016 } 38017 if (settings.oInit.searchPanes || 38018 DataTable.defaults.searchPanes) { 38019 if (!settings._searchPanes) { 38020 _init(settings, null, true); 38021 } 38022 } 38023 });
38024 // DataTables `dom` feature option 38025 DataTable.ext.feature.push({ 38026 cFeature: 'P', 38027 fnInit: _init 38028 }); 38029 // DataTables 2 layout feature 38030 if (DataTable.ext.features) { 38031 DataTable.ext.features.register('searchPanes', _init); 38032 } 38033 38034})(); 38035 38036 38037return DataTable; 38038})); 38039 38040 38041/*! Bootstrap integration for DataTables' SearchPanes 38042 * © SpryMedia Ltd - datatables.net/license 38043 */ 38044 38045(function( factory ){ 38046 if ( typeof define === 'function' && define.amd ) { 38047 // AMD 38048 define( ['jquery', 'datatables.net-dt', 'datatables.net-searchpanes'], function ( $ ) { 38049 return factory( $, window, document ); 38050 } ); 38051 } 38052 else if ( typeof exports === 'object' ) { 38053 // CommonJS 38054 var jq = require('jquery'); 38055 var cjsRequires = function (root, $) { 38056 if ( ! $.fn.dataTable ) { 38057 require('datatables.net-dt')(root, $); 38058 } 38059 38060 if ( ! $.fn.dataTable.SearchPanes ) { 38061 require('datatables.net-searchpanes')(root, $); 38062 } 38063 }; 38064 38065 if (typeof window !== 'undefined') { 38066 module.exports = function (root, $) { 38067 if ( ! root ) { 38068 // CommonJS environments without a window global must pass a 38069 // root. This will give an error otherwise 38070 root = window; 38071 } 38072 38073 if ( ! $ ) { 38074 $ = jq( root ); 38075 } 38076 38077 cjsRequires( root, $ ); 38078 return factory( $, root, root.document ); 38079 }; 38080 } 38081 else { 38082 cjsRequires( window, jq ); 38083 module.exports = factory( jq, window, window.document ); 38084 } 38085 } 38086 else { 38087 // Browser 38088 factory( jQuery, window, document ); 38089 } 38090}(function( $, window, document, undefined ) { 38091'use strict'; 38092var DataTable = $.fn.dataTable; 38093 38094 38095 38096 38097return DataTable; 38098})); 38099 38100 38101/*! Select for DataTables 1.6.2 38102 * © SpryMedia Ltd - datatables.net/license/mit 38103 */ 38104 38105(function( factory ){ 38106 if ( typeof define === 'function' && define.amd ) { 38107 // AMD 38108 define( ['jquery', 'datatables.net'], function ( $ ) { 38109 return factory( $, window, document ); 38110 } ); 38111 } 38112 else if ( typeof exports === 'object' ) { 38113 // CommonJS 38114 var jq = require('jquery'); 38115 var cjsRequires = function (root, $) { 38116 if ( ! $.fn.dataTable ) { 38117 require('datatables.net')(root, $); 38118 } 38119 }; 38120 38121 if (typeof window !== 'undefined') { 38122 module.exports = function (root, $) { 38123 if ( ! root ) { 38124 // CommonJS environments without a window global must pass a 38125 // root. This will give an error otherwise 38126 root = window; 38127 } 38128 38129 if ( ! $ ) { 38130 $ = jq( root ); 38131 } 38132 38133 cjsRequires( root, $ ); 38134 return factory( $, root, root.document ); 38135 }; 38136 } 38137 else { 38138 cjsRequires( window, jq ); 38139 module.exports = factory( jq, window, window.document ); 38140 } 38141 } 38142 else { 38143 // Browser 38144 factory( jQuery, window, document ); 38145 } 38146}(function( $, window, document, undefined ) { 38147'use strict'; 38148var DataTable = $.fn.dataTable; 38149 38150 38151 38152// Version information for debugger 38153DataTable.select = {}; 38154 38155DataTable.select.version = '1.6.2'; 38156 38157DataTable.select.init = function ( dt ) { 38158 var ctx = dt.settings()[0]; 38159 38160 if (ctx._select) { 38161 return; 38162 } 38163 38164 var savedSelected = dt.state.loaded(); 38165 38166 var selectAndSave = function(e, settings, data) { 38167 if(data === null || data.select === undefined) { 38168 return; 38169 } 38170 38171 // Clear any currently selected rows, before restoring state 38172 // None will be selected on first initialisation 38173 if (dt.rows({selected: true}).any()) { 38174 dt.rows().deselect(); 38175 } 38176 if (data.select.rows !== undefined) { 38177 dt.rows(data.select.rows).select(); 38178 } 38179 38180 if (dt.columns({selected: true}).any()) { 38181 dt.columns().deselect(); 38182 } 38183 if (data.select.columns !== undefined) { 38184 dt.columns(data.select.columns).select(); 38185 } 38186 38187 if (dt.cells({selected: true}).any()) { 38188 dt.cells().deselect(); 38189 } 38190 if (data.select.cells !== undefined) { 38191 for(var i = 0; i < data.select.cells.length; i++) { 38192 dt.cell(data.select.cells[i].row, data.select.cells[i].column).select(); 38193 } 38194 } 38195 38196 dt.state.save(); 38197 } 38198 38199 dt 38200 .on('stateSaveParams', function(e, settings, data) { 38201 data.select = {}; 38202 data.select.rows = dt.rows({selected:true}).ids(true).toArray(); 38203 data.select.columns = dt.columns({selected:true})[0]; 38204 data.select.cells = dt.cells({selected:true})[0].map(function(coords) { 38205 return {row: dt.row(coords.row).id(true), column: coords.column} 38206 }); 38207 }) 38208 .on('stateLoadParams', selectAndSave) 38209 .one('init', function() { 38210 selectAndSave(undefined, undefined, savedSelected); 38211 }); 38212 38213 var init = ctx.oInit.select; 38214 var defaults = DataTable.defaults.select; 38215 var opts = init === undefined ? 38216 defaults : 38217 init; 38218 38219 // Set defaults 38220 var items = 'row'; 38221 var style = 'api'; 38222 var blurable = false; 38223 var toggleable = true; 38224 var info = true; 38225 var selector = 'td, th'; 38226 var className = 'selected'; 38227 var setStyle = false; 38228 38229 ctx._select = {}; 38230 38231 // Initialisation customisations 38232 if ( opts === true ) { 38233 style = 'os'; 38234 setStyle = true; 38235 } 38236 else if ( typeof opts === 'string' ) { 38237 style = opts; 38238 setStyle = true; 38239 } 38240 else if ( $.isPlainObject( opts ) ) { 38241 if ( opts.blurable !== undefined ) { 38242 blurable = opts.blurable; 38243 } 38244 38245 if ( opts.toggleable !== undefined ) { 38246 toggleable = opts.toggleable; 38247 } 38248 38249 if ( opts.info !== undefined ) { 38250 info = opts.info; 38251 } 38252 38253 if ( opts.items !== undefined ) { 38254 items = opts.items; 38255 } 38256 38257 if ( opts.style !== undefined ) { 38258 style = opts.style; 38259 setStyle = true; 38260 } 38261 else { 38262 style = 'os'; 38263 setStyle = true; 38264 } 38265 38266 if ( opts.selector !== undefined ) { 38267 selector = opts.selector; 38268 } 38269 38270 if ( opts.className !== undefined ) { 38271 className = opts.className; 38272 } 38273 } 38274 38275 dt.select.selector( selector ); 38276 dt.select.items( items ); 38277 dt.select.style( style ); 38278 dt.select.blurable( blurable ); 38279 dt.select.toggleable( toggleable ); 38280 dt.select.info( info ); 38281 ctx._select.className = className; 38282 38283 38284 // Sort table based on selected rows. Requires Select Datatables extension 38285 $.fn.dataTable.ext.order['select-checkbox'] = function ( settings, col ) { 38286 return this.api().column( col, {order: 'index'} ).nodes().map( function ( td ) { 38287 if ( settings._select.items === 'row' ) { 38288 return $( td ).parent().hasClass( settings._select.className ); 38289 } else if ( settings._select.items === 'cell' ) { 38290 return $( td ).hasClass( settings._select.className ); 38291 } 38292 return false; 38293 }); 38294 }; 38295 38296 // If the init options haven't enabled select, but there is a selectable
38297 // class name, then enable 38298 if ( ! setStyle && $( dt.table().node() ).hasClass( 'selectable' ) ) { 38299 dt.select.style( 'os' ); 38300 } 38301}; 38302 38303/* 38304 38305Select is a collection of API methods, event handlers, event emitters and 38306buttons (for the `Buttons` extension) for DataTables. It provides the following 38307features, with an overview of how they are implemented: 38308 38309## Selection of rows, columns and cells. Whether an item is selected or not is 38310 stored in: 38311 38312* rows: a `_select_selected` property which contains a boolean value of the 38313 DataTables' `aoData` object for each row 38314* columns: a `_select_selected` property which contains a boolean value of the 38315 DataTables' `aoColumns` object for each column 38316* cells: a `_selected_cells` property which contains an array of boolean values 38317 of the `aoData` object for each row. The array is the same length as the 38318 columns array, with each element of it representing a cell. 38319 38320This method of using boolean flags allows Select to operate when nodes have not 38321been created for rows / cells (DataTables' defer rendering feature). 38322 38323## API methods 38324 38325A range of API methods are available for triggering selection and de-selection 38326of rows. Methods are also available to configure the selection events that can 38327be triggered by an end user (such as which items are to be selected). To a large 38328extent, these of API methods *is* Select. It is basically a collection of helper 38329functions that can be used to select items in a DataTable. 38330 38331Configuration of select is held in the object `_select` which is attached to the 38332DataTables settings object on initialisation. Select being available on a table 38333is not optional when Select is loaded, but its default is for selection only to 38334be available via the API - so the end user wouldn't be able to select rows 38335without additional configuration. 38336 38337The `_select` object contains the following properties: 38338 38339``` 38340{ 38341 items:string - Can be `rows`, `columns` or `cells`. Defines what item 38342 will be selected if the user is allowed to activate row 38343 selection using the mouse. 38344 style:string - Can be `none`, `single`, `multi` or `os`. Defines the 38345 interaction style when selecting items 38346 blurable:boolean - If row selection can be cleared by clicking outside of 38347 the table 38348 toggleable:boolean - If row selection can be cancelled by repeated clicking 38349 on the row 38350 info:boolean - If the selection summary should be shown in the table 38351 information elements 38352} 38353``` 38354 38355In addition to the API methods, Select also extends the DataTables selector 38356options for rows, columns and cells adding a `selected` option to the selector 38357options object, allowing the developer to select only selected items or 38358unselected items. 38359 38360## Mouse selection of items 38361 38362Clicking on items can be used to select items. This is done by a simple event 38363handler that will select the items using the API methods. 38364 38365 */ 38366 38367 38368/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 38369 * Local functions 38370 */ 38371 38372/** 38373 * Add one or more cells to the selection when shift clicking in OS selection 38374 * style cell selection. 38375 * 38376 * Cell range is more complicated than row and column as we want to select 38377 * in the visible grid rather than by index in sequence. For example, if you 38378 * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1 38379 * should also be selected (and not 1-3, 1-4. etc) 38380 * 38381 * @param {DataTable.Api} dt DataTable 38382 * @param {object} idx Cell index to select to 38383 * @param {object} last Cell index to select from 38384 * @private 38385 */ 38386function cellRange( dt, idx, last ) 38387{ 38388 var indexes; 38389 var columnIndexes; 38390 var rowIndexes; 38391 var selectColumns = function ( start, end ) { 38392 if ( start > end ) { 38393 var tmp = end; 38394 end = start; 38395 start = tmp; 38396 } 38397 38398 var record = false; 38399 return dt.columns( ':visible' ).indexes().filter( function (i) { 38400 if ( i === start ) { 38401 record = true; 38402 } 38403 38404 if ( i === end ) { // not else if, as start might === end 38405 record = false; 38406 return true; 38407 } 38408 38409 return record; 38410 } ); 38411 }; 38412 38413 var selectRows = function ( start, end ) { 38414 var indexes = dt.rows( { search: 'applied' } ).indexes(); 38415 38416 // Which comes first - might need to swap 38417 if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) { 38418 var tmp = end; 38419 end = start; 38420 start = tmp; 38421 } 38422 38423 var record = false; 38424 return indexes.filter( function (i) { 38425 if ( i === start ) { 38426 record = true; 38427 } 38428 38429 if ( i === end ) { 38430 record = false; 38431 return true; 38432 } 38433 38434 return record; 38435 } ); 38436 }; 38437 38438 if ( ! dt.cells( { selected: true } ).any() && ! last ) { 38439 // select from the top left cell to this one 38440 columnIndexes = selectColumns( 0, idx.column ); 38441 rowIndexes = selectRows( 0 , idx.row ); 38442 } 38443 else { 38444 // Get column indexes between old and new 38445 columnIndexes = selectColumns( last.column, idx.column ); 38446 rowIndexes = selectRows( last.row , idx.row ); 38447 } 38448 38449 indexes = dt.cells( rowIndexes, columnIndexes ).flatten(); 38450 38451 if ( ! dt.cells( idx, { selected: true } ).any() ) { 38452 // Select range 38453 dt.cells( indexes ).select(); 38454 } 38455 else { 38456 // Deselect range 38457 dt.cells( indexes ).deselect(); 38458 } 38459} 38460 38461/** 38462 * Disable mouse selection by removing the selectors 38463 * 38464 * @param {DataTable.Api} dt DataTable to remove events from 38465 * @private 38466 */ 38467function disableMouseSelection( dt ) 38468{ 38469 var ctx = dt.settings()[0]; 38470 var selector = ctx._select.selector; 38471 38472 $( dt.table().container() ) 38473 .off( 'mousedown.dtSelect', selector ) 38474 .off( 'mouseup.dtSelect', selector ) 38475 .off( 'click.dtSelect', selector ); 38476 38477 $('body').off( 'click.dtSelect' + _safeId(dt.table().node()) ); 38478} 38479 38480/** 38481 * Attach mouse listeners to the table to allow mouse selection of items 38482 * 38483 * @param {DataTable.Api} dt DataTable to remove events from 38484 * @private 38485 */ 38486function enableMouseSelection ( dt ) 38487{ 38488 var container = $( dt.table().container() ); 38489 var ctx = dt.settings()[0]; 38490 var selector = ctx._select.selector; 38491 var matchSelection; 38492 38493 container 38494 .on( 'mousedown.dtSelect', selector, function(e) { 38495 // Disallow text selection for shift clicking on the table so multi 38496 // element selection doesn't look terrible! 38497 if ( e.shiftKey || e.metaKey || e.ctrlKey ) { 38498 container 38499 .css( '-moz-user-select', 'none' ) 38500 .one('selectstart.dtSelect', selector, function () { 38501 return false; 38502 } ); 38503 } 38504 38505 if ( window.getSelection ) { 38506 matchSelection = window.getSelection(); 38507 } 38508 } ) 38509 .on( 'mouseup.dtSelect', selector, function() { 38510 // Allow text selection to occur again, Mozilla style (tested in FF 38511 // 35.0.1 - still required) 38512 container.css( '-moz-user-select', '' ); 38513 } ) 38514 .on( 'click.dtSelect', selector, function ( e ) { 38515 var items = dt.select.items();
38516 var idx; 38517 38518 // If text was selected (click and drag), then we shouldn't change 38519 // the row's selected state 38520 if ( matchSelection ) { 38521 var selection = window.getSelection(); 38522 38523 // If the element that contains the selection is not in the table, we can ignore it 38524 // This can happen if the developer selects text from the click event 38525 if ( ! selection.anchorNode || $(selection.anchorNode).closest('table')[0] === dt.table().node() ) { 38526 if ( selection !== matchSelection ) { 38527 return; 38528 } 38529 } 38530 } 38531 38532 var ctx = dt.settings()[0]; 38533 var wrapperClass = dt.settings()[0].oClasses.sWrapper.trim().replace(/ +/g, '.'); 38534 38535 // Ignore clicks inside a sub-table 38536 if ( $(e.target).closest('div.'+wrapperClass)[0] != dt.table().container() ) { 38537 return; 38538 } 38539 38540 var cell = dt.cell( $(e.target).closest('td, th') ); 38541 38542 // Check the cell actually belongs to the host DataTable (so child 38543 // rows, etc, are ignored) 38544 if ( ! cell.any() ) { 38545 return; 38546 } 38547 38548 var event = $.Event('user-select.dt'); 38549 eventTrigger( dt, event, [ items, cell, e ] ); 38550 38551 if ( event.isDefaultPrevented() ) { 38552 return; 38553 } 38554 38555 var cellIndex = cell.index(); 38556 if ( items === 'row' ) { 38557 idx = cellIndex.row; 38558 typeSelect( e, dt, ctx, 'row', idx ); 38559 } 38560 else if ( items === 'column' ) { 38561 idx = cell.index().column; 38562 typeSelect( e, dt, ctx, 'column', idx ); 38563 } 38564 else if ( items === 'cell' ) { 38565 idx = cell.index(); 38566 typeSelect( e, dt, ctx, 'cell', idx ); 38567 } 38568 38569 ctx._select_lastCell = cellIndex; 38570 } ); 38571 38572 // Blurable 38573 $('body').on( 'click.dtSelect' + _safeId(dt.table().node()), function ( e ) { 38574 if ( ctx._select.blurable ) { 38575 // If the click was inside the DataTables container, don't blur 38576 if ( $(e.target).parents().filter( dt.table().container() ).length ) { 38577 return; 38578 } 38579 38580 // Ignore elements which have been removed from the DOM (i.e. paging 38581 // buttons) 38582 if ( $(e.target).parents('html').length === 0 ) { 38583 return; 38584 } 38585 38586 // Don't blur in Editor form 38587 if ( $(e.target).parents('div.DTE').length ) { 38588 return; 38589 } 38590 38591 var event = $.Event('select-blur.dt'); 38592 eventTrigger( dt, event, [ e.target, e ] ); 38593 38594 if ( event.isDefaultPrevented() ) { 38595 return; 38596 } 38597 38598 clear( ctx, true ); 38599 } 38600 } ); 38601} 38602 38603/** 38604 * Trigger an event on a DataTable 38605 * 38606 * @param {DataTable.Api} api DataTable to trigger events on 38607 * @param {boolean} selected true if selected, false if deselected 38608 * @param {string} type Item type acting on 38609 * @param {boolean} any Require that there are values before 38610 * triggering 38611 * @private 38612 */ 38613function eventTrigger ( api, type, args, any ) 38614{ 38615 if ( any && ! api.flatten().length ) { 38616 return; 38617 } 38618 38619 if ( typeof type === 'string' ) { 38620 type = type +'.dt'; 38621 } 38622 38623 args.unshift( api ); 38624 38625 $(api.table().node()).trigger( type, args ); 38626} 38627 38628/** 38629 * Update the information element of the DataTable showing information about the 38630 * items selected. This is done by adding tags to the existing text 38631 * 38632 * @param {DataTable.Api} api DataTable to update 38633 * @private 38634 */ 38635function info ( api ) 38636{ 38637 var ctx = api.settings()[0]; 38638 38639 if ( ! ctx._select.info || ! ctx.aanFeatures.i ) { 38640 return; 38641 } 38642 38643 if ( api.select.style() === 'api' ) { 38644 return; 38645 } 38646 38647 var rows = api.rows( { selected: true } ).flatten().length; 38648 var columns = api.columns( { selected: true } ).flatten().length; 38649 var cells = api.cells( { selected: true } ).flatten().length; 38650 38651 var add = function ( el, name, num ) { 38652 el.append( $('<span class="select-item"/>').append( api.i18n( 38653 'select.'+name+'s', 38654 { _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' }, 38655 num 38656 ) ) ); 38657 }; 38658 38659 // Internal knowledge of DataTables to loop over all information elements 38660 $.each( ctx.aanFeatures.i, function ( i, el ) { 38661 el = $(el); 38662 38663 var output = $('<span class="select-info"/>'); 38664 add( output, 'row', rows ); 38665 add( output, 'column', columns ); 38666 add( output, 'cell', cells ); 38667 38668 var exisiting = el.children('span.select-info'); 38669 if ( exisiting.length ) { 38670 exisiting.remove(); 38671 } 38672 38673 if ( output.text() !== '' ) { 38674 el.append( output ); 38675 } 38676 } ); 38677} 38678 38679/** 38680 * Initialisation of a new table. Attach event handlers and callbacks to allow 38681 * Select to operate correctly. 38682 * 38683 * This will occur _after_ the initial DataTables initialisation, although 38684 * before Ajax data is rendered, if there is ajax data 38685 * 38686 * @param {DataTable.settings} ctx Settings object to operate on 38687 * @private 38688 */ 38689function init ( ctx ) { 38690 var api = new DataTable.Api( ctx ); 38691 ctx._select_init = true; 38692 38693 // Row callback so that classes can be added to rows and cells if the item 38694 // was selected before the element was created. This will happen with the 38695 // `deferRender` option enabled. 38696 // 38697 // This method of attaching to `aoRowCreatedCallback` is a hack until 38698 // DataTables has proper events for row manipulation If you are reviewing 38699 // this code to create your own plug-ins, please do not do this! 38700 ctx.aoRowCreatedCallback.push( { 38701 fn: function ( row, data, index ) { 38702 var i, ien; 38703 var d = ctx.aoData[ index ]; 38704 38705 // Row 38706 if ( d._select_selected ) { 38707 $( row ).addClass( ctx._select.className ); 38708 } 38709 38710 // Cells and columns - if separated out, we would need to do two 38711 // loops, so it makes sense to combine them into a single one 38712 for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) { 38713 if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) { 38714 $(d.anCells[i]).addClass( ctx._select.className ); 38715 } 38716 } 38717 }, 38718 sName: 'select-deferRender' 38719 } ); 38720 38721 // On Ajax reload we want to reselect all rows which are currently selected, 38722 // if there is an rowId (i.e. a unique value to identify each row with) 38723 api.on( 'preXhr.dt.dtSelect', function (e, settings) { 38724 if (settings !== api.settings()[0]) { 38725 // Not triggered by our DataTable! 38726 return; 38727 } 38728 38729 // note that column selection doesn't need to be cached and then 38730 // reselected, as they are already selected 38731 var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) { 38732 return d !== undefined; 38733 } ); 38734 38735 var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) { 38736 var id = api.row( cellIdx.row ).id( true ); 38737 return id ? 38738 { row: id, column: cellIdx.column } : 38739 undefined; 38740 } ).filter( function ( d ) { 38741 return d !== undefined; 38742 } ); 38743 38744 // On the next draw, reselect the currently selected items 38745 api.one( 'draw.dt.dtSelect', function () { 38746 api.rows( rows ).select(); 38747 38748 // `cells` is not a cell index selector, so it needs a loop 38749 if ( cells.any() ) { 38750 cells.each( function ( id ) { 38751 api.cells( id.row, id.column ).select(); 38752 } ); 38753 } 38754 } ); 38755 } ); 38756
38757 // Update the table information element with selected item summary 38758 api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () { 38759 info( api ); 38760 api.state.save(); 38761 } ); 38762 38763 // Clean up and release 38764 api.on( 'destroy.dtSelect', function () { 38765 // Remove class directly rather than calling deselect - which would trigger events 38766 $(api.rows({selected: true}).nodes()).removeClass(api.settings()[0]._select.className); 38767 38768 disableMouseSelection( api ); 38769 api.off( '.dtSelect' ); 38770 $('body').off('.dtSelect' + _safeId(api.table().node())); 38771 } ); 38772} 38773 38774/** 38775 * Add one or more items (rows or columns) to the selection when shift clicking 38776 * in OS selection style 38777 * 38778 * @param {DataTable.Api} dt DataTable 38779 * @param {string} type Row or column range selector 38780 * @param {object} idx Item index to select to 38781 * @param {object} last Item index to select from 38782 * @private 38783 */ 38784function rowColumnRange( dt, type, idx, last ) 38785{ 38786 // Add a range of rows from the last selected row to this one 38787 var indexes = dt[type+'s']( { search: 'applied' } ).indexes(); 38788 var idx1 = $.inArray( last, indexes ); 38789 var idx2 = $.inArray( idx, indexes ); 38790 38791 if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) { 38792 // select from top to here - slightly odd, but both Windows and Mac OS 38793 // do this 38794 indexes.splice( $.inArray( idx, indexes )+1, indexes.length ); 38795 } 38796 else { 38797 // reverse so we can shift click 'up' as well as down 38798 if ( idx1 > idx2 ) { 38799 var tmp = idx2; 38800 idx2 = idx1; 38801 idx1 = tmp; 38802 } 38803 38804 indexes.splice( idx2+1, indexes.length ); 38805 indexes.splice( 0, idx1 ); 38806 } 38807 38808 if ( ! dt[type]( idx, { selected: true } ).any() ) { 38809 // Select range 38810 dt[type+'s']( indexes ).select(); 38811 } 38812 else { 38813 // Deselect range - need to keep the clicked on row selected 38814 indexes.splice( $.inArray( idx, indexes ), 1 ); 38815 dt[type+'s']( indexes ).deselect(); 38816 } 38817} 38818 38819/** 38820 * Clear all selected items 38821 * 38822 * @param {DataTable.settings} ctx Settings object of the host DataTable 38823 * @param {boolean} [force=false] Force the de-selection to happen, regardless 38824 * of selection style 38825 * @private 38826 */ 38827function clear( ctx, force ) 38828{ 38829 if ( force || ctx._select.style === 'single' ) { 38830 var api = new DataTable.Api( ctx ); 38831 38832 api.rows( { selected: true } ).deselect(); 38833 api.columns( { selected: true } ).deselect(); 38834 api.cells( { selected: true } ).deselect(); 38835 } 38836} 38837 38838/** 38839 * Select items based on the current configuration for style and items. 38840 * 38841 * @param {object} e Mouse event object 38842 * @param {DataTables.Api} dt DataTable 38843 * @param {DataTable.settings} ctx Settings object of the host DataTable 38844 * @param {string} type Items to select 38845 * @param {int|object} idx Index of the item to select 38846 * @private 38847 */ 38848function typeSelect ( e, dt, ctx, type, idx ) 38849{ 38850 var style = dt.select.style(); 38851 var toggleable = dt.select.toggleable(); 38852 var isSelected = dt[type]( idx, { selected: true } ).any(); 38853 38854 if ( isSelected && ! toggleable ) { 38855 return; 38856 } 38857 38858 if ( style === 'os' ) { 38859 if ( e.ctrlKey || e.metaKey ) { 38860 // Add or remove from the selection 38861 dt[type]( idx ).select( ! isSelected ); 38862 } 38863 else if ( e.shiftKey ) { 38864 if ( type === 'cell' ) { 38865 cellRange( dt, idx, ctx._select_lastCell || null ); 38866 } 38867 else { 38868 rowColumnRange( dt, type, idx, ctx._select_lastCell ? 38869 ctx._select_lastCell[type] : 38870 null 38871 ); 38872 } 38873 } 38874 else { 38875 // No cmd or shift click - deselect if selected, or select 38876 // this row only 38877 var selected = dt[type+'s']( { selected: true } ); 38878 38879 if ( isSelected && selected.flatten().length === 1 ) { 38880 dt[type]( idx ).deselect(); 38881 } 38882 else { 38883 selected.deselect(); 38884 dt[type]( idx ).select(); 38885 } 38886 } 38887 } else if ( style == 'multi+shift' ) { 38888 if ( e.shiftKey ) { 38889 if ( type === 'cell' ) { 38890 cellRange( dt, idx, ctx._select_lastCell || null ); 38891 } 38892 else { 38893 rowColumnRange( dt, type, idx, ctx._select_lastCell ? 38894 ctx._select_lastCell[type] : 38895 null 38896 ); 38897 } 38898 } 38899 else {
38900 dt[ type ]( idx ).select( ! isSelected ); 38901 } 38902 } 38903 else { 38904 dt[ type ]( idx ).select( ! isSelected ); 38905 } 38906} 38907 38908function _safeId( node ) { 38909 return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-'); 38910} 38911 38912 38913 38914/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 38915 * DataTables selectors 38916 */ 38917 38918// row and column are basically identical just assigned to different properties 38919// and checking a different array, so we can dynamically create the functions to 38920// reduce the code size 38921$.each( [ 38922 { type: 'row', prop: 'aoData' }, 38923 { type: 'column', prop: 'aoColumns' } 38924], function ( i, o ) { 38925 DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) { 38926 var selected = opts.selected; 38927 var data; 38928 var out = []; 38929 38930 if ( selected !== true && selected !== false ) { 38931 return indexes; 38932 } 38933 38934 for ( var i=0, ien=indexes.length ; i<ien ; i++ ) { 38935 data = settings[ o.prop ][ indexes[i] ]; 38936 38937 if ( (selected === true && data._select_selected === true) || 38938 (selected === false && ! data._select_selected ) 38939 ) { 38940 out.push( indexes[i] ); 38941 } 38942 } 38943 38944 return out; 38945 } ); 38946} ); 38947 38948DataTable.ext.selector.cell.push( function ( settings, opts, cells ) { 38949 var selected = opts.selected; 38950 var rowData; 38951 var out = []; 38952 38953 if ( selected === undefined ) { 38954 return cells; 38955 } 38956 38957 for ( var i=0, ien=cells.length ; i<ien ; i++ ) { 38958 rowData = settings.aoData[ cells[i].row ]; 38959 38960 if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) || 38961 (selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) ) 38962 ) { 38963 out.push( cells[i] ); 38964 } 38965 } 38966 38967 return out; 38968} ); 38969 38970 38971 38972/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 38973 * DataTables API 38974 * 38975 * For complete documentation, please refer to the docs/api directory or the 38976 * DataTables site 38977 */ 38978 38979// Local variables to improve compression 38980var apiRegister = DataTable.Api.register; 38981var apiRegisterPlural = DataTable.Api.registerPlural; 38982 38983apiRegister( 'select()', function () { 38984 return this.iterator( 'table', function ( ctx ) { 38985 DataTable.select.init( new DataTable.Api( ctx ) ); 38986 } ); 38987} ); 38988 38989apiRegister( 'select.blurable()', function ( flag ) { 38990 if ( flag === undefined ) { 38991 return this.context[0]._select.blurable; 38992 } 38993 38994 return this.iterator( 'table', function ( ctx ) { 38995 ctx._select.blurable = flag; 38996 } ); 38997} ); 38998 38999apiRegister( 'select.toggleable()', function ( flag ) { 39000 if ( flag === undefined ) { 39001 return this.context[0]._select.toggleable; 39002 } 39003 39004 return this.iterator( 'table', function ( ctx ) { 39005 ctx._select.toggleable = flag; 39006 } ); 39007} ); 39008 39009apiRegister( 'select.info()', function ( flag ) { 39010 if ( flag === undefined ) { 39011 return this.context[0]._select.info; 39012 } 39013 39014 return this.iterator( 'table', function ( ctx ) { 39015 ctx._select.info = flag; 39016 } ); 39017} ); 39018 39019apiRegister( 'select.items()', function ( items ) { 39020 if ( items === undefined ) { 39021 return this.context[0]._select.items; 39022 } 39023 39024 return this.iterator( 'table', function ( ctx ) { 39025 ctx._select.items = items; 39026 39027 eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] ); 39028 } ); 39029} ); 39030 39031// Takes effect from the _next_ selection. None disables future selection, but 39032// does not clear the current selection. Use the `deselect` methods for that 39033apiRegister( 'select.style()', function ( style ) { 39034 if ( style === undefined ) { 39035 return this.context[0]._select.style; 39036 } 39037 39038 return this.iterator( 'table', function ( ctx ) { 39039 if ( ! ctx._select ) { 39040 DataTable.select.init( new DataTable.Api(ctx) ); 39041 } 39042 39043 if ( ! ctx._select_init ) { 39044 init(ctx); 39045 } 39046 39047 ctx._select.style = style; 39048 39049 // Add / remove mouse event handlers. They aren't required when only 39050 // API selection is available 39051 var dt = new DataTable.Api( ctx ); 39052 disableMouseSelection( dt ); 39053 39054 if ( style !== 'api' ) { 39055 enableMouseSelection( dt ); 39056 } 39057 39058 eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] ); 39059 } ); 39060} ); 39061 39062apiRegister( 'select.selector()', function ( selector ) { 39063 if ( selector === undefined ) { 39064 return this.context[0]._select.selector; 39065 } 39066 39067 return this.iterator( 'table', function ( ctx ) { 39068 disableMouseSelection( new DataTable.Api( ctx ) ); 39069 39070 ctx._select.selector = selector; 39071 39072 if ( ctx._select.style !== 'api' ) { 39073 enableMouseSelection( new DataTable.Api( ctx ) ); 39074 } 39075 } ); 39076} ); 39077 39078 39079 39080apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) { 39081 var api = this; 39082 39083 if ( select === false ) { 39084 return this.deselect(); 39085 } 39086 39087 this.iterator( 'row', function ( ctx, idx ) { 39088 clear( ctx ); 39089 39090 ctx.aoData[ idx ]._select_selected = true; 39091 $( ctx.aoData[ idx ].nTr ).addClass( ctx._select.className ); 39092 } ); 39093 39094 this.iterator( 'table', function ( ctx, i ) { 39095 eventTrigger( api, 'select', [ 'row', api[i] ], true ); 39096 } ); 39097 39098 return this; 39099} ); 39100 39101apiRegister( 'row().selected()', function () { 39102 var ctx = this.context[0]; 39103 39104 if ( 39105 ctx && 39106 this.length && 39107 ctx.aoData[this[0]] && 39108 ctx.aoData[this[0]]._select_selected 39109 ) { 39110 return true; 39111 } 39112 39113 return false; 39114} ); 39115 39116apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) { 39117 var api = this; 39118 39119 if ( select === false ) { 39120 return this.deselect(); 39121 } 39122 39123 this.iterator( 'column', function ( ctx, idx ) { 39124 clear( ctx ); 39125 39126 ctx.aoColumns[ idx ]._select_selected = true; 39127 39128 var column = new DataTable.Api( ctx ).column( idx ); 39129 39130 $( column.header() ).addClass( ctx._select.className ); 39131 $( column.footer() ).addClass( ctx._select.className ); 39132 39133 column.nodes().to$().addClass( ctx._select.className ); 39134 } ); 39135 39136 this.iterator( 'table', function ( ctx, i ) { 39137 eventTrigger( api, 'select', [ 'column', api[i] ], true ); 39138 } ); 39139 39140 return this; 39141} ); 39142 39143apiRegister( 'column().selected()', function () { 39144 var ctx = this.context[0]; 39145 39146 if ( 39147 ctx && 39148 this.length && 39149 ctx.aoColumns[this[0]] && 39150 ctx.aoColumns[this[0]]._select_selected 39151 ) { 39152 return true; 39153 } 39154 39155 return false; 39156} ); 39157 39158apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) { 39159 var api = this; 39160 39161 if ( select === false ) { 39162 return this.deselect(); 39163 } 39164 39165 this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) { 39166 clear( ctx ); 39167 39168 var data = ctx.aoData[ rowIdx ]; 39169 39170 if ( data._selected_cells === undefined ) { 39171 data._selected_cells = []; 39172 } 39173 39174 data._selected_cells[ colIdx ] = true; 39175 39176 if ( data.anCells ) {
39177 $( data.anCells[ colIdx ] ).addClass( ctx._select.className ); 39178 } 39179 } ); 39180 39181 this.iterator( 'table', function ( ctx, i ) { 39182 eventTrigger( api, 'select', [ 'cell', api.cells(api[i]).indexes().toArray() ], true ); 39183 } ); 39184 39185 return this; 39186} ); 39187 39188apiRegister( 'cell().selected()', function () { 39189 var ctx = this.context[0]; 39190 39191 if (ctx && this.length) { 39192 var row = ctx.aoData[this[0][0].row]; 39193 39194 if (row && row._selected_cells && row._selected_cells[this[0][0].column]) { 39195 return true; 39196 } 39197 } 39198 39199 return false; 39200} ); 39201 39202 39203apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () { 39204 var api = this; 39205 39206 this.iterator( 'row', function ( ctx, idx ) { 39207 ctx.aoData[ idx ]._select_selected = false; 39208 ctx._select_lastCell = null; 39209 $( ctx.aoData[ idx ].nTr ).removeClass( ctx._select.className ); 39210 } ); 39211 39212 this.iterator( 'table', function ( ctx, i ) { 39213 eventTrigger( api, 'deselect', [ 'row', api[i] ], true ); 39214 } ); 39215 39216 return this; 39217} ); 39218 39219apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () { 39220 var api = this; 39221 39222 this.iterator( 'column', function ( ctx, idx ) { 39223 ctx.aoColumns[ idx ]._select_selected = false; 39224 39225 var api = new DataTable.Api( ctx ); 39226 var column = api.column( idx ); 39227 39228 $( column.header() ).removeClass( ctx._select.className ); 39229 $( column.footer() ).removeClass( ctx._select.className ); 39230 39231 // Need to loop over each cell, rather than just using 39232 // `column().nodes()` as cells which are individually selected should 39233 // not have the `selected` class removed from them 39234 api.cells( null, idx ).indexes().each( function (cellIdx) { 39235 var data = ctx.aoData[ cellIdx.row ]; 39236 var cellSelected = data._selected_cells; 39237 39238 if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) { 39239 $( data.anCells[ cellIdx.column ] ).removeClass( ctx._select.className ); 39240 } 39241 } ); 39242 } ); 39243 39244 this.iterator( 'table', function ( ctx, i ) { 39245 eventTrigger( api, 'deselect', [ 'column', api[i] ], true ); 39246 } ); 39247 39248 return this; 39249} ); 39250 39251apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () { 39252 var api = this; 39253 39254 this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) { 39255 var data = ctx.aoData[ rowIdx ]; 39256 39257 if(data._selected_cells !== undefined) { 39258 data._selected_cells[ colIdx ] = false; 39259 } 39260 39261 // Remove class only if the cells exist, and the cell is not column 39262 // selected, in which case the class should remain (since it is selected 39263 // in the column) 39264 if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) { 39265 $( data.anCells[ colIdx ] ).removeClass( ctx._select.className ); 39266 } 39267 } ); 39268 39269 this.iterator( 'table', function ( ctx, i ) { 39270 eventTrigger( api, 'deselect', [ 'cell', api[i] ], true ); 39271 } ); 39272 39273 return this; 39274} ); 39275 39276 39277 39278/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 39279 * Buttons 39280 */ 39281function i18n( label, def ) { 39282 return function (dt) { 39283 return dt.i18n( 'buttons.'+label, def ); 39284 }; 39285} 39286 39287// Common events with suitable namespaces 39288function namespacedEvents ( config ) { 39289 var unique = config._eventNamespace; 39290 39291 return 'draw.dt.DT'+unique+' select.dt.DT'+unique+' deselect.dt.DT'+unique; 39292} 39293 39294function enabled ( dt, config ) { 39295 if ( $.inArray( 'rows', config.limitTo ) !== -1 && dt.rows( { selected: true } ).any() ) { 39296 return true; 39297 } 39298 39299 if ( $.inArray( 'columns', config.limitTo ) !== -1 && dt.columns( { selected: true } ).any() ) { 39300 return true; 39301 } 39302 39303 if ( $.inArray( 'cells', config.limitTo ) !== -1 && dt.cells( { selected: true } ).any() ) { 39304 return true; 39305 } 39306 39307 return false; 39308} 39309 39310var _buttonNamespace = 0; 39311 39312$.extend( DataTable.ext.buttons, { 39313 selected: { 39314 text: i18n( 'selected', 'Selected' ), 39315 className: 'buttons-selected', 39316 limitTo: [ 'rows', 'columns', 'cells' ], 39317 init: function ( dt, node, config ) { 39318 var that = this; 39319 config._eventNamespace = '.select'+(_buttonNamespace++); 39320 39321 // .DT namespace listeners are removed by DataTables automatically 39322 // on table destroy 39323 dt.on( namespacedEvents(config), function () { 39324 that.enable( enabled(dt, config) ); 39325 } ); 39326 39327 this.disable(); 39328 }, 39329 destroy: function ( dt, node, config ) { 39330 dt.off( config._eventNamespace ); 39331 } 39332 }, 39333 selectedSingle: { 39334 text: i18n( 'selectedSingle', 'Selected single' ), 39335 className: 'buttons-selected-single', 39336 init: function ( dt, node, config ) { 39337 var that = this;
39338 config._eventNamespace = '.select'+(_buttonNamespace++); 39339 39340 dt.on( namespacedEvents(config), function () { 39341 var count = dt.rows( { selected: true } ).flatten().length + 39342 dt.columns( { selected: true } ).flatten().length + 39343 dt.cells( { selected: true } ).flatten().length; 39344 39345 that.enable( count === 1 ); 39346 } ); 39347 39348 this.disable(); 39349 }, 39350 destroy: function ( dt, node, config ) { 39351 dt.off( config._eventNamespace ); 39352 } 39353 }, 39354 selectAll: { 39355 text: i18n( 'selectAll', 'Select all' ), 39356 className: 'buttons-select-all', 39357 action: function () { 39358 var items = this.select.items(); 39359 this[ items+'s' ]().select(); 39360 } 39361 }, 39362 selectNone: { 39363 text: i18n( 'selectNone', 'Deselect all' ), 39364 className: 'buttons-select-none', 39365 action: function () { 39366 clear( this.settings()[0], true ); 39367 }, 39368 init: function ( dt, node, config ) { 39369 var that = this; 39370 config._eventNamespace = '.select'+(_buttonNamespace++); 39371 39372 dt.on( namespacedEvents(config), function () { 39373 var count = dt.rows( { selected: true } ).flatten().length + 39374 dt.columns( { selected: true } ).flatten().length + 39375 dt.cells( { selected: true } ).flatten().length; 39376 39377 that.enable( count > 0 ); 39378 } ); 39379 39380 this.disable(); 39381 }, 39382 destroy: function ( dt, node, config ) { 39383 dt.off( config._eventNamespace ); 39384 } 39385 }, 39386 showSelected: { 39387 text: i18n( 'showSelected', 'Show only selected' ), 39388 className: 'buttons-show-selected', 39389 action: function (e, dt, node, conf) { 39390 // Works by having a filtering function which will reduce to the selected 39391 // items only. So we can re-reference the function it gets stored in the 39392 // `conf` object 39393 if (conf._filter) { 39394 var idx = DataTable.ext.search.indexOf(conf._filter); 39395 39396 if (idx !== -1) { 39397 DataTable.ext.search.splice(idx, 1); 39398 conf._filter = null; 39399 } 39400 39401 this.active(false); 39402 } 39403 else { 39404 var fn = function (s, data, idx) { 39405 // Need to be sure we are operating on our table! 39406 if (s !== dt.settings()[0]) { 39407 return true; 39408 } 39409 39410 let row = s.aoData[idx]; 39411 39412 return row._select_selected; 39413 } 39414 39415 conf._filter = fn; 39416 DataTable.ext.search.push(fn); 39417 39418 this.active(true); 39419 } 39420 39421 dt.draw(); 39422 } 39423 } 39424} ); 39425 39426$.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) { 39427 var lc = item.toLowerCase(); 39428 39429 DataTable.ext.buttons[ 'select'+item+'s' ] = { 39430 text: i18n( 'select'+item+'s', 'Select '+lc+'s' ), 39431 className: 'buttons-select-'+lc+'s', 39432 action: function () { 39433 this.select.items( lc ); 39434 }, 39435 init: function ( dt ) { 39436 var that = this; 39437 39438 dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) { 39439 that.active( items === lc ); 39440 } ); 39441 } 39442 }; 39443} ); 39444 39445 39446$.fn.DataTable.select = DataTable.select; 39447 39448/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 39449 * Initialisation 39450 */ 39451 39452// DataTables creation - check if select has been defined in the options. Note 39453// this required that the table be in the document! If it isn't then something 39454// needs to trigger this method unfortunately. The next major release of 39455// DataTables will rework the events and address this. 39456$(document).on( 'preInit.dt.dtSelect', function (e, ctx) { 39457 if ( e.namespace !== 'dt' ) { 39458 return; 39459 } 39460 39461 DataTable.select.init( new DataTable.Api( ctx ) ); 39462} ); 39463 39464 39465return DataTable; 39466})); 39467 39468 39469/*! StateRestore 1.2.2 39470 * © SpryMedia Ltd - datatables.net/license 39471 */ 39472 39473(function( factory ){ 39474 if ( typeof define === 'function' && define.amd ) { 39475 // AMD 39476 define( ['jquery', 'datatables.net'], function ( $ ) { 39477 return factory( $, window, document ); 39478 } ); 39479 } 39480 else if ( typeof exports === 'object' ) { 39481 // CommonJS 39482 var jq = require('jquery'); 39483 var cjsRequires = function (root, $) { 39484 if ( ! $.fn.dataTable ) { 39485 require('datatables.net')(root, $); 39486 } 39487 }; 39488 39489 if (typeof window !== 'undefined') { 39490 module.exports = function (root, $) { 39491 if ( ! root ) { 39492 // CommonJS environments without a window global must pass a 39493 // root. This will give an error otherwise 39494 root = window; 39495 } 39496 39497 if ( ! $ ) { 39498 $ = jq( root ); 39499 } 39500 39501 cjsRequires( root, $ ); 39502 return factory( $, root, root.document ); 39503 }; 39504 } 39505 else { 39506 cjsRequires( window, jq ); 39507 module.exports = factory( jq, window, window.document ); 39508 } 39509 } 39510 else { 39511 // Browser 39512 factory( jQuery, window, document ); 39513 } 39514}(function( $, window, document, undefined ) { 39515'use strict'; 39516var DataTable = $.fn.dataTable; 39517 39518 39519(function () { 39520 'use strict'; 39521 39522 var $$2; 39523 var dataTable$1; 39524 function setJQuery$1(jq) { 39525 $$2 = jq; 39526 dataTable$1 = jq.fn.dataTable; 39527 } 39528 var StateRestore = /** @class */ (function () { 39529 function StateRestore(settings, opts, identifier, state, isPreDefined, successCallback) { 39530 if (state === void 0) { state = undefined; } 39531 if (isPreDefined === void 0) { isPreDefined = false; } 39532 if (successCallback === void 0) { successCallback = function () { return null; }; }
39533 // Check that the required version of DataTables is included 39534 if (!dataTable$1 || !dataTable$1.versionCheck || !dataTable$1.versionCheck('1.10.0')) { 39535 throw new Error('StateRestore requires DataTables 1.10 or newer'); 39536 } 39537 // Check that Select is included 39538 // eslint-disable-next-line no-extra-parens 39539 if (!dataTable$1.Buttons) { 39540 throw new Error('StateRestore requires Buttons'); 39541 } 39542 var table = new dataTable$1.Api(settings); 39543 this.classes = $$2.extend(true, {}, StateRestore.classes); 39544 // Get options from user 39545 this.c = $$2.extend(true, {}, StateRestore.defaults, opts); 39546 this.s = { 39547 dt: table, 39548 identifier: identifier, 39549 isPreDefined: isPreDefined, 39550 savedState: null, 39551 tableId: state && state.stateRestore ? state.stateRestore.tableId : undefined 39552 }; 39553 this.dom = { 39554 background: $$2('<div class="' + this.classes.background + '"/>'), 39555 closeButton: $$2('<div class="' + this.classes.closeButton + '">x</div>'), 39556 confirmation: $$2('<div class="' + this.classes.confirmation + '"/>'), 39557 confirmationButton: $$2('<button class="' + this.classes.confirmationButton + ' ' + this.classes.dtButton + '">'), 39558 confirmationTitleRow: $$2('<div class="' + this.classes.confirmationTitleRow + '"></div>'), 39559 dtContainer: $$2(this.s.dt.table().container()), 39560 duplicateError: $$2('<span class="' + this.classes.modalError + '">' + 39561 this.s.dt.i18n('stateRestore.duplicateError', this.c.i18n.duplicateError) + 39562 '</span>'), 39563 emptyError: $$2('<span class="' + this.classes.modalError + '">' + 39564 this.s.dt.i18n('stateRestore.emptyError', this.c.i18n.emptyError) + 39565 '</span>'), 39566 removeContents: $$2('<div class="' + this.classes.confirmationText + '"><span>' + 39567 this.s.dt 39568 .i18n('stateRestore.removeConfirm', this.c.i18n.removeConfirm) 39569 .replace(/%s/g, this.s.identifier) + 39570 '</span></div>'), 39571 removeError: $$2('<span class="' + this.classes.modalError + '">' + 39572 this.s.dt.i18n('stateRestore.removeError', this.c.i18n.removeError) + 39573 '</span>'), 39574 removeTitle: $$2('<h2 class="' + this.classes.confirmationTitle + '">' + 39575 this.s.dt.i18n('stateRestore.removeTitle', this.c.i18n.removeTitle) + 39576 '</h2>'), 39577 renameContents: $$2('<div class="' + this.classes.confirmationText + ' ' + this.classes.renameModal + '">' + 39578 '<label class="' + this.classes.confirmationMessage + '">' + 39579 this.s.dt 39580 .i18n('stateRestore.renameLabel', this.c.i18n.renameLabel) 39581 .replace(/%s/g, this.s.identifier) + 39582 '</label>' + 39583 '</div>'), 39584 renameInput: $$2('<input class="' + this.classes.input + '" type="text"></input>'), 39585 renameTitle: $$2('<h2 class="' + this.classes.confirmationTitle + '">' + 39586 this.s.dt.i18n('stateRestore.renameTitle', this.c.i18n.renameTitle) + 39587 '</h2>') 39588 }; 39589 // When a StateRestore instance is created the current state of the table should also be saved. 39590 this.save(state, successCallback); 39591 } 39592 /** 39593 * Removes a state from storage and then triggers the dtsr-remove event 39594 * so that the StateRestoreCollection class can remove it's references as well. 39595 * 39596 * @param skipModal Flag to indicate if the modal should be skipped or not 39597 */ 39598 StateRestore.prototype.remove = function (skipModal) { 39599 var _a; 39600 var _this = this; 39601 if (skipModal === void 0) { skipModal = false; } 39602 // Check if removal of states is allowed 39603 if (!this.c.remove) { 39604 return false; 39605 } 39606 var removeFunction; 39607 var ajaxData = { 39608 action: 'remove', 39609 stateRestore: (_a = {}, 39610 _a[this.s.identifier] = this.s.savedState, 39611 _a) 39612 }; 39613 var successCallback = function () { 39614 _this.dom.confirmation.trigger('dtsr-remove'); 39615 $$2(_this.s.dt.table().node()).trigger('stateRestore-change'); 39616 _this.dom.background.click(); 39617 _this.dom.confirmation.remove();
39618 $$2(document).unbind('keyup', function (e) { return _this._keyupFunction(e); }); 39619 _this.dom.confirmationButton.off('click'); 39620 }; 39621 // If the remove is not happening over ajax remove it from local storage and then trigger the event 39622 if (!this.c.ajax) { 39623 removeFunction = function () { 39624 try { 39625 localStorage.removeItem('DataTables_stateRestore_' + _this.s.identifier + '_' + location.pathname + 39626 (_this.s.tableId ? '_' + _this.s.tableId : '')); 39627 successCallback(); 39628 } 39629 catch (e) { 39630 _this.dom.confirmation.children('.' + _this.classes.modalError).remove(); 39631 _this.dom.confirmation.append(_this.dom.removeError); 39632 return 'remove'; 39633 } 39634 return true; 39635 }; 39636 } 39637 // Ajax property has to be a string, not just true 39638 // Also only want to save if the table has been initialised and the states have been loaded in 39639 else if (typeof this.c.ajax === 'string' && this.s.dt.settings()[0]._bInitComplete) { 39640 removeFunction = function () { 39641 $$2.ajax({ 39642 data: ajaxData, 39643 success: successCallback, 39644 type: 'POST', 39645 url: _this.c.ajax 39646 }); 39647 return true; 39648 }; 39649 } 39650 else if (typeof this.c.ajax === 'function') { 39651 removeFunction = function () { 39652 if (typeof _this.c.ajax === 'function') { 39653 _this.c.ajax.call(_this.s.dt, ajaxData, successCallback); 39654 } 39655 return true; 39656 }; 39657 } 39658 // If the modal is to be skipped then remove straight away 39659 if (skipModal) { 39660 this.dom.confirmation.appendTo(this.dom.dtContainer); 39661 $$2(this.s.dt.table().node()).trigger('dtsr-modal-inserted'); 39662 removeFunction(); 39663 this.dom.confirmation.remove(); 39664 } 39665 // Otherwise display the modal 39666 else { 39667 this._newModal(this.dom.removeTitle, this.s.dt.i18n('stateRestore.removeSubmit', this.c.i18n.removeSubmit), removeFunction, this.dom.removeContents); 39668 } 39669 return true; 39670 }; 39671 /** 39672 * Compares the state held within this instance with a state that is passed in 39673 * 39674 * @param state The state that is to be compared against 39675 * @returns boolean indicating if the states match 39676 */ 39677 StateRestore.prototype.compare = function (state) { 39678 // Order 39679 if (!this.c.saveState.order) { 39680 state.order = undefined; 39681 } 39682 // Search 39683 if (!this.c.saveState.search) { 39684 state.search = undefined; 39685 } 39686 // Columns 39687 if (this.c.saveState.columns && state.columns) { 39688 for (var i = 0, ien = state.columns.length; i < ien; i++) { 39689 // Visibility 39690 if (typeof this.c.saveState.columns !== 'boolean' && !this.c.saveState.columns.visible) { 39691 state.columns[i].visible = undefined; 39692 } 39693 // Search 39694 if (typeof this.c.saveState.columns !== 'boolean' && !this.c.saveState.columns.search) { 39695 state.columns[i].search = undefined; 39696 } 39697 } 39698 } 39699 else if (!this.c.saveState.columns) { 39700 state.columns = undefined; 39701 } 39702 // Paging 39703 if (!this.c.saveState.paging) { 39704 state.page = undefined; 39705 } 39706 // SearchBuilder 39707 if (!this.c.saveState.searchBuilder) { 39708 state.searchBuilder = undefined; 39709 } 39710 // SearchPanes 39711 if (!this.c.saveState.searchPanes) { 39712 state.searchPanes = undefined; 39713 } 39714 // Select 39715 if (!this.c.saveState.select) { 39716 state.select = undefined; 39717 } 39718 // ColReorder 39719 if (!this.c.saveState.colReorder) { 39720 state.ColReorder = undefined; 39721 } 39722 // Scroller 39723 if (!this.c.saveState.scroller) { 39724 state.scroller = undefined; 39725 if (dataTable$1.Scroller !== undefined) { 39726 state.start = 0; 39727 } 39728 } 39729 // Paging 39730 if (!this.c.saveState.paging) { 39731 state.start = 0; 39732 } 39733 // Page Length 39734 if (!this.c.saveState.length) { 39735 state.length = undefined; 39736 } 39737 // Need to delete properties that we do not want to compare 39738 delete state.time; 39739 var copyState = this.s.savedState; 39740 delete copyState.time; 39741 delete copyState.c;
39742 delete copyState.stateRestore; 39743 // Perform a deep compare of the two state objects 39744 return this._deepCompare(state, copyState); 39745 }; 39746 /** 39747 * Removes all of the dom elements from the document 39748 */ 39749 StateRestore.prototype.destroy = function () { 39750 Object.values(this.dom).forEach(function (node) { return node.off().remove(); }); 39751 }; 39752 /** 39753 * Loads the state referenced by the identifier from storage 39754 * 39755 * @param state The identifier of the state that should be loaded 39756 * @returns the state that has been loaded 39757 */ 39758 StateRestore.prototype.load = function () { 39759 var _this = this; 39760 var loadedState = this.s.savedState; 39761 var settings = this.s.dt.settings()[0]; 39762 // Always want the states stored here to be loaded in - regardless of when they were created 39763 loadedState.time = +new Date(); 39764 settings.oLoadedState = $$2.extend(true, {}, loadedState); 39765 // Click on a background if there is one to shut the collection 39766 $$2('div.dt-button-background').click(); 39767 // Call the internal datatables function to implement the state on the table 39768 $$2.fn.dataTable.ext.oApi._fnImplementState(settings, loadedState, function () { 39769 var correctPaging = function (e, preSettings) { 39770 setTimeout(function () { 39771 var currpage = preSettings._iDisplayStart / preSettings._iDisplayLength; 39772 var intendedPage = loadedState.start / loadedState.length; 39773 // If the paging is incorrect then we have to set it again so that it is correct 39774 // This happens when a searchpanes filter is removed 39775 // This has to happen in a timeout because searchpanes only deselects after a timeout 39776 if (currpage >= 0 && intendedPage >= 0 && currpage !== intendedPage) { 39777 _this.s.dt.page(intendedPage).draw(false); 39778 } 39779 }, 50); 39780 }; 39781 _this.s.dt.one('preDraw', correctPaging); 39782 _this.s.dt.draw(false); 39783 }); 39784 return loadedState; 39785 }; 39786 /** 39787 * Shows a modal that allows a state to be renamed 39788 * 39789 * @param newIdentifier Optional. The new identifier for this state 39790 */ 39791 StateRestore.prototype.rename = function (newIdentifier, currentIdentifiers) { 39792 var _this = this; 39793 if (newIdentifier === void 0) { newIdentifier = null; } 39794 // Check if renaming of states is allowed 39795 if (!this.c.rename) { 39796 return; 39797 } 39798 var renameFunction = function () { 39799 var _a; 39800 if (newIdentifier === null) { 39801 var tempIdentifier = $$2('input.' + _this.classes.input.replace(/ /g, '.')).val(); 39802 if (tempIdentifier.length === 0) { 39803 _this.dom.confirmation.children('.' + _this.classes.modalError).remove(); 39804 _this.dom.confirmation.append(_this.dom.emptyError); 39805 return 'empty'; 39806 } 39807 else if (currentIdentifiers.includes(tempIdentifier)) { 39808 _this.dom.confirmation.children('.' + _this.classes.modalError).remove(); 39809 _this.dom.confirmation.append(_this.dom.duplicateError); 39810 return 'duplicate'; 39811 } 39812 else { 39813 newIdentifier = tempIdentifier; 39814 } 39815 } 39816 var ajaxData = { 39817 action: 'rename', 39818 stateRestore: (_a = {}, 39819 _a[_this.s.identifier] = newIdentifier, 39820 _a) 39821 }; 39822 var successCallback = function () { 39823 _this.s.identifier = newIdentifier; 39824 _this.save(_this.s.savedState, function () { return null; }, false); 39825 _this.dom.removeContents = $$2('<div class="' + _this.classes.confirmationText
39825+ '"><span>' + 39826 _this.s.dt 39827 .i18n('stateRestore.removeConfirm', _this.c.i18n.removeConfirm) 39828 .replace(/%s/g, _this.s.identifier) + 39829 '</span></div>'); 39830 _this.dom.confirmation.trigger('dtsr-rename'); 39831 _this.dom.background.click(); 39832 _this.dom.confirmation.remove(); 39833 $$2(document).unbind('keyup', function (e) { return _this._keyupFunction(e); }); 39834 _this.dom.confirmationButton.off('click'); 39835 }; 39836 if (!_this.c.ajax) { 39837 try { 39838 localStorage.removeItem('DataTables_stateRestore_' + _this.s.identifier + '_' + location.pathname + 39839 (_this.s.tableId ? '_' + _this.s.tableId : '')); 39840 successCallback(); 39841 } 39842 catch (e) { 39843 _this.dom.confirmation.children('.' + _this.classes.modalError).remove(); 39844 _this.dom.confirmation.append(_this.dom.removeError); 39845 return false; 39846 } 39847 } 39848 else if (typeof _this.c.ajax === 'string' && _this.s.dt.settings()[0]._bInitComplete) { 39849 $$2.ajax({ 39850 data: ajaxData, 39851 success: successCallback, 39852 type: 'POST', 39853 url: _this.c.ajax 39854 }); 39855 } 39856 else if (typeof _this.c.ajax === 'function') { 39857 _this.c.ajax.call(_this.s.dt, ajaxData, successCallback); 39858 } 39859 return true; 39860 }; 39861 // Check if a new identifier has been provided, if so no need for a modal 39862 if (newIdentifier !== null) { 39863 if (currentIdentifiers.includes(newIdentifier)) { 39864 throw new Error(this.s.dt.i18n('stateRestore.duplicateError', this.c.i18n.duplicateError)); 39865 } 39866 else if (newIdentifier.length === 0) { 39867 throw new Error(this.s.dt.i18n('stateRestore.emptyError', this.c.i18n.emptyError)); 39868 } 39869 else { 39870 this.dom.confirmation.appendTo(this.dom.dtContainer); 39871 $$2(this.s.dt.table().node()).trigger('dtsr-modal-inserted'); 39872 renameFunction(); 39873 this.dom.confirmation.remove(); 39874 } 39875 } 39876 else { 39877 this.dom.renameInput.val(this.s.identifier); 39878 this.dom.renameContents.append(this.dom.renameInput); 39879 this._newModal(this.dom.renameTitle, this.s.dt.i18n('stateRestore.renameButton', this.c.i18n.renameButton), renameFunction, this.dom.renameContents); 39880 } 39881 }; 39882 /** 39883 * Saves the tables current state using the identifier that is passed in. 39884 * 39885 * @param state Optional. If provided this is the state that will be saved rather than using the current state 39886 */ 39887 StateRestore.prototype.save = function (state, passedSuccessCallback, callAjax) { 39888 var _a; 39889 var _this = this; 39890 if (callAjax === void 0) { callAjax = true; } 39891 // Check if saving states is allowed 39892 if (!this.c.save) { 39893 if (passedSuccessCallback) { 39894 passedSuccessCallback.call(this); 39895 } 39896 return; 39897 } 39898 // this.s.dt.state.save(); 39899 var savedState; 39900 // If no state has been provided then create a new one from the current state 39901 this.s.dt.state.save(); 39902 if (state === undefined) { 39903 savedState = this.s.dt.state(); 39904 } 39905 else if (typeof state !== 'object') { 39906 return; 39907 } 39908 else { 39909 savedState = state; 39910 } 39911 if (savedState.stateRestore) { 39912 savedState.stateRestore.isPreDefined = this.s.isPreDefined; 39913 savedState.stateRestore.state = this.s.identifier; 39914 savedState.stateRestore.tableId = this.s.tableId; 39915 } 39916 else { 39917 savedState.stateRestore = { 39918 isPreDefined: this.s.isPreDefined, 39919 state: this.s.identifier, 39920 tableId: this.s.tableId 39921 }; 39922 } 39923 this.s.savedState = savedState; 39924 // Order 39925 if (!this.c.saveState.order) { 39926 this.s.savedState.order = undefined; 39927 } 39928 // Search 39929 if (!this.c.saveState.search) { 39930 this.s.savedState.search = undefined; 39931 } 39932 // Columns 39933 if (this.c.saveState.columns && this.s.savedState.columns) { 39934 for (var i = 0, ien = this.s.savedState.columns.length; i < ien; i++) { 39935 // Visibility 39936 if (typeof this.c.saveState.columns !== 'boolean' && !this.c.saveState.columns.visible) { 39937 this.s.savedState.columns[i].visible = undefined; 39938 } 39939 // Search 39940 if (typeof this.c.saveState.columns !== 'boolean' && !this.c.saveState.columns.search) { 39941 this.s.savedState.columns[i].search = undefined; 39942 } 39943 } 39944 } 39945 else if (!this.c.saveState.columns) { 39946 this.s.savedState.columns = undefined; 39947 } 39948 // SearchBuilder 39949 if (!this.c.saveState.searchBuilder) { 39950 this.s.savedState.searchBuilder = undefined; 39951 } 39952 // SearchPanes 39953 if (!this.c.saveState.searchPanes) { 39954 this.s.savedState.searchPanes = undefined; 39955 } 39956 // Select 39957 if (!this.c.saveState.select) { 39958 this.s.savedState.select = undefined; 39959 } 39960 // ColReorder 39961 if (!this.c.saveState.colReorder) { 39962 this.s.savedState.ColReorder = undefined; 39963 } 39964 // Scroller 39965 if (!this.c.saveState.scroller) { 39966 this.s.savedState.scroller = undefined; 39967 if (dataTable$1.Scroller !== undefined) { 39968 this.s.savedState.start = 0; 39969 } 39970 } 39971 // Paging 39972 if (!this.c.saveState.paging) { 39973 this.s.savedState.start = 0; 39974 } 39975 // Page Length 39976 if (!this.c.saveState.length) { 39977 this.s.savedState.length = undefined; 39978 } 39979 this.s.savedState.c = this.c; 39980 // Need to remove the parent reference before we save the state 39981 // Its not needed to rebuild, but it does cause a circular reference when convert
39981ing to JSON 39982 if (this.s.savedState.c.splitSecondaries.length) { 39983 for (var _i = 0, _b = this.s.savedState.c.splitSecondaries; _i < _b.length; _i++) { 39984 var secondary = _b[_i]; 39985 if (secondary.parent) { 39986 secondary.parent = undefined; 39987 } 39988 } 39989 } 39990 // If the state is predefined there is no need to save it over ajax or to local storage 39991 if (this.s.isPreDefined) { 39992 if (passedSuccessCallback) { 39993 passedSuccessCallback.call(this); 39994 } 39995 return; 39996 } 39997 var ajaxData = { 39998 action: 'save', 39999 stateRestore: (_a = {}, 40000 _a[this.s.identifier] = this.s.savedState, 40001 _a) 40002 }; 40003 var successCallback = function () { 40004 if (passedSuccessCallback) { 40005 passedSuccessCallback.call(_this); 40006 } 40007 _this.dom.confirmation.trigger('dtsr-save'); 40008 $$2(_this.s.dt.table().node()).trigger('stateRestore-change'); 40009 }; 40010 if (!this.c.ajax) { 40011 localStorage.setItem('DataTables_stateRestore_' + this.s.identifier + '_' + location.pathname + 40012 (this.s.tableId ? '_' + this.s.tableId : ''), JSON.stringify(this.s.savedState)); 40013 successCallback(); 40014 } 40015 else if (typeof this.c.ajax === 'string' && callAjax) { 40016 if (this.s.dt.settings()[0]._bInitComplete) { 40017 $$2.ajax({ 40018 data: ajaxData, 40019 success: successCallback, 40020 type: 'POST', 40021 url: this.c.ajax 40022 }); 40023 } 40024 else { 40025 this.s.dt.one('init', function () { 40026 $$2.ajax({ 40027 data: ajaxData, 40028 success: successCallback, 40029 type: 'POST', 40030 url: _this.c.ajax 40031 }); 40032 }); 40033 } 40034 } 40035 else if (typeof this.c.ajax === 'function' && callAjax) { 40036 this.c.ajax.call(this.s.dt, ajaxData, successCallback); 40037 } 40038 }; 40039 /** 40040 * Performs a deep compare of two state objects, returning true if they match 40041 * 40042 * @param state1 The first object to compare 40043 * @param state2 The second object to compare 40044 * @returns boolean indicating if the objects match 40045 */ 40046 StateRestore.prototype._deepCompare = function (state1, state2) { 40047 // Put keys and states into arrays as this makes the later code easier to work 40048 var states = [state1, state2]; 40049 var keys = [Object.keys(state1).sort(), Object.keys(state2).sort()]; 40050 // If scroller is included then we need to remove the start value 40051 // as it can be different but yield the same results 40052 if (keys[0].includes('scroller')) { 40053 var startIdx = keys[0].indexOf('start'); 40054 if (startIdx) { 40055 keys[0].splice(startIdx, 1); 40056 } 40057 } 40058 if (keys[1].includes('scroller')) { 40059 var startIdx = keys[1].indexOf('start'); 40060 if (startIdx) { 40061 keys[1].splice(startIdx, 1); 40062 } 40063 } 40064 // We want to remove any private properties within the states 40065 for (var i = 0; i < keys[0].length; i++) { 40066 if (keys[0][i].indexOf('_') === 0) { 40067 keys[0].splice(i, 1); 40068 i--; 40069 continue; 40070 } 40071 // If scroller is included then we need to remove the following values 40072 // as they can be different but yield the same results 40073 if (keys[0][i] === 'baseRowTop' || 40074 keys[0][i] === 'baseScrollTop' || 40075 keys[0][i] === 'scrollTop' || 40076 (!this.c.saveState.paging && keys[0][i] === 'page')) { 40077 keys[0].splice(i, 1); 40078 i--; 40079 continue; 40080 } 40081 } 40082 for (var i = 0; i < keys[1].length; i++) { 40083 if (keys[1][i].indexOf('_') === 0) { 40084 keys[1].splice(i, 1); 40085 i--; 40086 continue; 40087 } 40088 if (keys[1][i] === 'baseRowTop' || 40089 keys[1][i] === 'baseScrollTop' || 40090 keys[1][i] === 'scrollTop' || 40091 (!this.c.saveState.paging && keys[0][i] === 'page')) { 40092 keys[1].splice(i, 1); 40093 i--; 40094 continue; 40095 } 40096 } 40097 if (keys[0].length === 0 && keys[1].length > 0 || 40098 keys[1].length === 0 && keys[0].length > 0) { 40099 return false; 40100 } 40101 // We are only going to compare the keys that are common between both states 40102 for (var i = 0; i < keys[0].length; i++) { 40103 if (!keys[1].includes(keys[0][i])) { 40104 keys[0].splice(i, 1); 40105 i--; 40106 } 40107 } 40108 for (var i = 0; i < keys[1].length; i++) { 40109 if (!keys[0].includes(keys[1][i])) { 40110 keys[1].splice(i, 1); 40111 i--; 40112 } 40113 } 40114 // Then each key and value has to be checked against each other 40115 for (var i = 0; i < keys[0].length; i++) { 40116 // If the keys dont equal, or their corresponding types are different we can return false 40117 if (keys[0][i] !== keys[1][i] || typeof states[0][keys[0][i]] !== typeof states[1][keys[1][i]]) { 40118 return false; 40119 } 40120 // If the type is an object then further deep comparisons are required 40121 if (typeof states[0][keys[0][i]] === 'object') { 40122 if (!this._deepCompare(states[0][keys[0][i]], states[1][keys[1][i]])) { 40123 return false; 40124 } 40125 } 40126 else if (typeof states[0][keys[0][i]] === 'number' && typeof states[1][keys[1][i]] === 'number') { 40127 if (Math.round(states[0][keys[0][i]]) !== Math.round(states[1][keys[1][i]])) { 40128 return false; 40129 } 40130 } 40131 // Otherwise we can just check the value 40132 else if (states[0][keys[0][i]] !== states[1][keys[1][i]]) { 40133 return false; 40134 } 40135 } 40136 // If we get all the way to here there are no differences so return true for this object 40137 return true; 40138 }; 40139 StateRestore.prototype._keyupFunction = function (e) { 40140 // If enter same action as pressing the button 40141 if (e.key === 'Enter') { 40142 this.dom.confirmationButton.click(); 40143 } 40144 // If escape close modal 40145 else if (e.key === 'Escape') { 40146 $$2('div.' + this.classes.background.replace(/ /g, '.')).click(); 40147 } 40148 }; 40149 /** 40150 * Creates a new confirmation modal for the user to approve an action 40151 * 40152 * @param title The title that is to be displayed at the top of the modal 40153 * @param buttonText The text that is to be displayed in the confirmation button of the modal 40154 * @param buttonAction The action that should be taken when the confirmation button is pressed 40155 * @param modalContents The contents for the main body of the modal 40156 */ 40157 StateRestore.prototype._newModal = function (title, buttonText, buttonAction, modalContents) { 40158 var _this = this; 40159 this.dom.background.appendTo(this.dom.dtContainer); 40160 this.dom.confirmationTitleRow.empty().append(title); 40161 this.dom.confirmationButton.html(buttonText); 40162 this.dom.confirmation 40163 .empty() 40164 .append(this.dom.confirmationTitleRow) 40165 .append(modalContents) 40166 .append($$2('<div class="' + this.classes.confirmationButtons + '"></div>') 40167 .append(this.dom.confirmationButton)) 40168 .appendTo(this.dom.dtContainer); 40169 $$2(this.s.dt.table().node()).trigger('dtsr-modal-inserted'); 40170 var inputs = modalContents.children('input'); 40171 // If there is an input focus on that 40172 if (inputs.length > 0) { 40173 $$2(inputs[0]).focus(); 40174 } 40175 // Otherwise focus on the confirmation button 40176 else { 40177 this.dom.confirmationButton.focus(); 40178 } 40179 var background = $$2('div.' + this.classes.background.replace(/ /g, '.')); 40180 if (this.c.modalCloseButton) { 40181 this.dom.confirmation.append(this.dom.closeButton); 40182 this.dom.closeButton.on('click', function () { return background.click(); }); 40183 } 40184 // When the button is clicked, call the appropriate action, 40185 // remove the background and modal from the screen and unbind the keyup event. 40186 this.dom.confirmationButton.on('click', function () { return buttonAction(); }); 40187 this.dom.confirmation.on('click', function (e) { 40188 e.stopPropagation(); 40189 }); 40190 // When the button is clicked, remove the background and modal from the screen and unbind the keyup event. 40191 background.one('click', function () { 40192 _this.dom.background.remove(); 40193 _this.dom.confirmation.remove();
40194 $$2(document).unbind('keyup', function (e) { return _this._keyupFunction(e); }); 40195 }); 40196 $$2(document).on('keyup', function (e) { return _this._keyupFunction(e); }); 40197 }; 40198 /** 40199 * Convert from camelCase notation to the internal Hungarian. 40200 * We could use the Hungarian convert function here, but this is cleaner 40201 * 40202 * @param {object} obj Object to convert 40203 * @returns {object} Inverted object 40204 * @memberof DataTable#oApi 40205 */ 40206 StateRestore.prototype._searchToHung = function (obj) { 40207 return { 40208 bCaseInsensitive: obj.caseInsensitive, 40209 bRegex: obj.regex, 40210 bSmart: obj.smart, 40211 sSearch: obj.search 40212 }; 40213 }; 40214 StateRestore.version = '1.2.2'; 40215 StateRestore.classes = { 40216 background: 'dtsr-background', 40217 closeButton: 'dtsr-popover-close', 40218 confirmation: 'dtsr-confirmation', 40219 confirmationButton: 'dtsr-confirmation-button', 40220 confirmationButtons: 'dtsr-confirmation-buttons', 40221 confirmationMessage: 'dtsr-confirmation-message dtsr-name-label', 40222 confirmationText: 'dtsr-confirmation-text', 40223 confirmationTitle: 'dtsr-confirmation-title', 40224 confirmationTitleRow: 'dtsr-confirmation-title-row', 40225 dtButton: 'dt-button', 40226 input: 'dtsr-input', 40227 modalError: 'dtsr-modal-error', 40228 renameModal: 'dtsr-rename-modal' 40229 }; 40230 StateRestore.defaults = { 40231 _createInSaved: false, 40232 ajax: false, 40233 create: true, 40234 creationModal: false, 40235 i18n: { 40236 creationModal: { 40237 button: 'Create', 40238 colReorder: 'Column Order:', 40239 columns: { 40240 search: 'Column Search:', 40241 visible: 'Column Visibility:' 40242 }, 40243 length: 'Page Length:', 40244 name: 'Name:', 40245 order: 'Sorting:', 40246 paging: 'Paging:', 40247 scroller: 'Scroll Position:', 40248 search: 'Search:', 40249 searchBuilder: 'SearchBuilder:', 40250 searchPanes: 'SearchPanes:', 40251 select: 'Select:', 40252 title: 'Create New State', 40253 toggleLabel: 'Includes:' 40254 }, 40255 duplicateError: 'A state with this name already exists.', 40256 emptyError: 'Name cannot be empty.', 40257 emptyStates: 'No saved states', 40258 removeConfirm: 'Are you sure you want to remove %s?', 40259 removeError: 'Failed to remove state.', 40260 removeJoiner: ' and ', 40261 removeSubmit: 'Remove', 40262 removeTitle: 'Remove State', 40263 renameButton: 'Rename', 40264 renameLabel: 'New Name for %s:', 40265 renameTitle: 'Rename State' 40266 }, 40267 modalCloseButton: true, 40268 remove: true, 40269 rename: true, 40270 save: true, 40271 saveState: { 40272 colReorder: true, 40273 columns: { 40274 search: true, 40275 visible: true 40276 }, 40277 length: true, 40278 order: true, 40279 paging: true, 40280 scroller: true, 40281 search: true, 40282 searchBuilder: true, 40283 searchPanes: true, 40284 select: true 40285 }, 40286 splitSecondaries: [ 40287 'updateState', 40288 'renameState', 40289 'removeState' 40290 ], 40291 toggle: { 40292 colReorder: false, 40293 columns: { 40294 search: false, 40295 visible: false 40296 }, 40297 length: false, 40298 order: false, 40299 paging: false, 40300 scroller: false, 40301 search: false, 40302 searchBuilder: false, 40303 searchPanes: false, 40304 select: false 40305 } 40306 }; 40307 return StateRestore; 40308 }()); 40309 40310 var $$1; 40311 var dataTable; 40312 function setJQuery(jq) { 40313 $$1 = jq; 40314 dataTable = jq.fn.dataTable; 40315 } 40316 var StateRestoreCollection = /** @class */ (function () { 40317 function StateRestoreCollection(settings, opts) { 40318 var _this = this;
40319 // Check that the required version of DataTables is included 40320 if (!dataTable || !dataTable.versionCheck || !dataTable.versionCheck('1.10.0')) { 40321 throw new Error('StateRestore requires DataTables 1.10 or newer'); 40322 } 40323 // Check that Select is included 40324 // eslint-disable-next-line no-extra-parens 40325 if (!dataTable.Buttons) { 40326 throw new Error('StateRestore requires Buttons'); 40327 } 40328 var table = new dataTable.Api(settings); 40329 this.classes = $$1.extend(true, {}, StateRestoreCollection.classes); 40330 if (table.settings()[0]._stateRestore !== undefined) { 40331 return; 40332 } 40333 // Get options from user 40334 this.c = $$1.extend(true, {}, StateRestoreCollection.defaults, opts); 40335 this.s = { 40336 dt: table, 40337 hasColReorder: dataTable.ColReorder !== undefined, 40338 hasScroller: dataTable.Scroller !== undefined, 40339 hasSearchBuilder: dataTable.SearchBuilder !== undefined, 40340 hasSearchPanes: dataTable.SearchPanes !== undefined, 40341 hasSelect: dataTable.select !== undefined, 40342 states: [] 40343 }; 40344 this.s.dt.on('xhr', function (e, xhrsettings, json) { 40345 // Has staterestore been used before? Is there anything to load? 40346 if (json && json.stateRestore) { 40347 _this._addPreDefined(json.stateRestore); 40348 } 40349 }); 40350 this.dom = { 40351 background: $$1('<div class="' + this.classes.background + '"/>'), 40352 closeButton: $$1('<div class="' + this.classes.closeButton + '">x</div>'), 40353 colReorderToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40354 '<input type="checkbox" class="' + 40355 this.classes.colReorderToggle + ' ' + 40356 this.classes.checkBox + 40357 '" checked>' + 40358 '<label class="' + this.classes.checkLabel + '">' + 40359 this.s.dt.i18n('stateRestore.creationModal.colReorder', this.c.i18n.creationModal.colReorder) + 40360 '</label>' + 40361 '</div>'), 40362 columnsSearchToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40363 '<input type="checkbox" class="' + 40364 this.classes.columnsSearchToggle + ' ' + 40365 this.classes.checkBox + 40366 '" checked>' + 40367 '<label class="' + this.classes.checkLabel + '">' + 40368 this.s.dt.i18n('stateRestore.creationModal.columns.search', this.c.i18n.creationModal.columns.search) + 40369 '</label>' + 40370 '</div>'), 40371 columnsVisibleToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + ' ' + '">' + 40372 '<input type="checkbox" class="' + 40373 this.classes.columnsVisibleToggle + ' ' + 40374 this.classes.checkBox + 40375 '" checked>' + 40376 '<label class="' + this.classes.checkLabel + '">' + 40377 this.s.dt.i18n('stateRestore.creationModal.columns.visible', this.c.i18n.creationModal.columns.visible) + 40378 '</label>' + 40379 '</div>'), 40380 confirmation: $$1('<div class="' + this.classes.confirmation + '"/>'), 40381 confirmationTitleRow: $$1('<div class="' + this.classes.confirmationTitleRow + '"></div>'), 40382 createButtonRow: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.modalFoot + '">' + 40383 '<button class="' + this.classes.creationButton + ' ' + this.classes.dtButton + '">' + 40384 this.s.dt.i18n('stateRestore.creationModal.button', this.c.i18n.creationModal.button) + 40385 '</button>' + 40386 '</div>'), 40387 creation: $$1('<div class="' + this.classes.creation + '"/>'), 40388 creationForm: $$1('<div class="' + this.classes.creationForm + '"/>'), 40389 creationTitle: $$1('<div class="' + this.classes.creationText
40389+ '">' + 40390 '<h2 class="' + this.classes.creationTitle + '">' + 40391 this.s.dt.i18n('stateRestore.creationModal.title', this.c.i18n.creationModal.title) + 40392 '</h2>' + 40393 '</div>'), 40394 dtContainer: $$1(this.s.dt.table().container()), 40395 duplicateError: $$1('<span class="' + this.classes.modalError + '">' + 40396 this.s.dt.i18n('stateRestore.duplicateError', this.c.i18n.duplicateError) + 40397 '</span>'), 40398 emptyError: $$1('<span class="' + this.classes.modalError + '">' + 40399 this.s.dt.i18n('stateRestore.emptyError', this.c.i18n.emptyError) + 40400 '</span>'), 40401 lengthToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40402 '<input type="checkbox" class="' + 40403 this.classes.lengthToggle + ' ' + 40404 this.classes.checkBox + 40405 '" checked>' + 40406 '<label class="' + this.classes.checkLabel + '">' + 40407 this.s.dt.i18n('stateRestore.creationModal.length', this.c.i18n.creationModal.length) + 40408 '</label>' + 40409 '</div>'), 40410 nameInputRow: $$1('<div class="' + this.classes.formRow + '">' + 40411 '<label class="' + this.classes.nameLabel + '">' + 40412 this.s.dt.i18n('stateRestore.creationModal.name', this.c.i18n.creationModal.name) + 40413 '</label>' + 40414 '<input class="' + this.classes.nameInput + '" type="text">' + 40415 '</div>'), 40416 orderToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40417 '<input type="checkbox" class="' + 40418 this.classes.orderToggle + ' ' + 40419 this.classes.checkBox + 40420 '" checked>' + 40421 '<label class="' + this.classes.checkLabel + '">' + 40422 this.s.dt.i18n('stateRestore.creationModal.order', this.c.i18n.creationModal.order) + 40423 '</label>' + 40424 '</div>'), 40425 pagingToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40426 '<input type="checkbox" class="' + 40427 this.classes.pagingToggle + ' ' + 40428 this.classes.checkBox + 40429 '" checked>' + 40430 '<label class="' + this.classes.checkLabel + '">' + 40431 this.s.dt.i18n('stateRestore.creationModal.paging', this.c.i18n.creationModal.paging) + 40432 '</label>' + 40433 '</div>'), 40434 removeContents: $$1('<div class="' + this.classes.confirmationText + '"><span></span></div>'), 40435 removeTitle: $$1('<div class="' + this.classes.creationText + '">' + 40436 '<h2 class="' + this.classes.creationTitle + '">' + 40437 this.s.dt.i18n('stateRestore.removeTitle', this.c.i18n.removeTitle) + 40438 '</h2>' + 40439 '</div>'), 40440 scrollerToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40441 '<input type="checkbox" class="' + 40442 this.classes.scrollerToggle + ' ' + 40443 this.classes.checkBox + 40444 '" checked>' + 40445 '<label class="' + this.classes.checkLabel + '">' + 40446 this.s.dt.i18n('stateRestore.creationModal.scroller', this.c.i18n.creationModal.scroller) + 40447 '</label>' + 40448 '</div>'), 40449 searchBuilderToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40450 '<input type="checkbox" class="' + 40451 this.classes.searchBuilderToggle + ' ' + 40452 this.classes.checkBox + 40453 '" checked>' + 40454 '<label class="' + this.classes.checkLabel + '">' + 40455 this.s.dt.i18n('stateRestore.creationModal.searchBuilder', this.c.i18n.creationModal.searchBuilder) + 40456 '</label>' + 40457 '</div>'), 40458 searchPanesToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40459 '<input type="checkbox" class="' + 40460 this.classes.searchPanesToggle + ' ' + 40461 this.classes.checkBox + 40462 '" checked>' + 40463 '<label class="' + this.classes.checkLabel + '">' + 40464 this.s.dt.i18n('stateRestore.creationModal.searchPanes', this.c.i18n.creationModal.searchPanes) + 40465 '</label>' + 40466 '</div>'), 40467 searchToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40468 '<input type="checkbox" class="' + 40469 this.classes.searchToggle + ' ' + 40470 this.classes.checkBox + 40471 '" checked>' + 40472 '<label class="' + this.classes.checkLabel + '">' + 40473 this.s.dt.i18n('stateRestore.creationModal.search', this.c.i18n.creationModal.search) + 40474 '</label>' + 40475 '</div>'), 40476 selectToggle: $$1('<div class="' + this.classes.formRow + ' ' + this.classes.checkRow + '">' + 40477 '<input type="checkbox" class="' + 40478 this.classes.selectToggle + ' ' + 40479 this.classes.checkBox + 40480 '" checked>' + 40481 '<label class="' + this.classes.checkLabel + '">' + 40482 this.s.dt.i18n('stateRestore.creationModal.select', this.c.i18n.creationModal.select) + 40483 '</label>' + 40484 '</div>'), 40485 toggleLabel: $$1('<label class="' + this.classes.nameLabel + ' ' + this.classes.toggleLabel + '">' + 40486 this.s.dt.i18n('stateRestore.creationModal.toggleLabel', this.c.i18n.creationModal.toggleLabel) + 40487 '</label>') 40488 }; 40489 table.settings()[0]._stateRestore = this; 40490 this._searchForStates(); 40491 // Has staterestore been used before? Is there anything to load? 40492 this._addPreDefined(this.c.preDefined); 40493 var ajaxFunction; 40494 var ajaxData = { 40495 action: 'load' 40496 }; 40497 if (typeof this.c.ajax === 'function') { 40498 ajaxFunction = function () { 40499 if (typeof _this.c.ajax === 'function') { 40500 _this.c.ajax.call(_this.s.dt, ajaxData, function (s) { return _this._addPreDefined(s); }); 40501 } 40502 }; 40503 } 40504 else if (typeof this.c.ajax === 'string') { 40505 ajaxFunction = function () { 40506 $$1.ajax({ 40507 data: ajaxData, 40508 success: function (data) { 40509 _this._addPreDefined(data); 40510 }, 40511 type: 'POST', 40512 url: _this.c.ajax 40513 }); 40514 }; 40515 } 40516 if (typeof ajaxFunction === 'function') { 40517 if (this.s.dt.settings()[0]._bInitComplete) { 40518 ajaxFunction(); 40519 } 40520 else { 40521 this.s.dt.one('preInit.dtsr', function () { 40522 ajaxFunction(); 40523 }); 40524 } 40525 } 40526 this.s.dt.on('destroy.dtsr', function () { 40527 _this.destroy(); 40528 }); 40529 this.s.dt.on('draw.dtsr buttons-action.dtsr', function () { return _this.findActive(); }); 40530 return this; 40531 } 40532 /**
40533 * Adds a new StateRestore instance to the collection based on the current properties of the table 40534 * 40535 * @param identifier The value that is used to identify a state. 40536 * @returns The state that has been created 40537 */ 40538 StateRestoreCollection.prototype.addState = function (identifier, currentIdentifiers, options) { 40539 var _this = this; 40540 // If creation/saving is not allowed then return 40541 if (!this.c.create || !this.c.save) { 40542 return; 40543 } 40544 // Check if the state exists before creating a new ones 40545 var state = this.getState(identifier); 40546 var createFunction = function (id, toggles) { 40547 if (id.length === 0) { 40548 return 'empty'; 40549 } 40550 else if (currentIdentifiers.includes(id)) { 40551 return 'duplicate'; 40552 } 40553 _this.s.dt.state.save(); 40554 var that = _this; 40555 var successCallback = function () { 40556 that.s.states.push(this); 40557 that._collectionRebuild(); 40558 }; 40559 var currState = _this.s.dt.state(); 40560 currState.stateRestore = { 40561 isPredefined: false, 40562 state: id, 40563 tableId: _this.s.dt.table().node().id 40564 }; 40565 if (toggles.saveState) { 40566 var opts = _this.c.saveState; 40567 // We don't want to extend, but instead AND all properties of the saveState option 40568 for (var _i = 0, _a = Object.keys(toggles.saveState); _i < _a.length; _i++) { 40569 var key = _a[_i]; 40570 if (!toggles.saveState[key]) { 40571 opts[key] = false; 40572 } 40573 } 40574 _this.c.saveState = opts; 40575 } 40576 var newState = new StateRestore(_this.s.dt.settings()[0], $$1.extend(true, {}, _this.c, options), id, currState, false, successCallback); 40577 $$1(_this.s.dt.table().node()).on('dtsr-modal-inserted', function () { 40578 newState.dom.confirmation.one('dtsr-remove', function () { return _this._removeCallback(newState.s.identifier); }); 40579 newState.dom.confirmation.one('dtsr-rename', function () { return _this._collectionRebuild(); }); 40580 newState.dom.confirmation.one('dtsr-save', function () { return _this._collectionRebuild(); }); 40581 }); 40582 return true; 40583 }; 40584 // If there isn't already a state with this identifier 40585 if (state === null) { 40586 if (this.c.creationModal || options !== undefined && options.creationModal) { 40587 this._creationModal(createFunction, identifier, options); 40588 } 40589 else { 40590 var success = createFunction(identifier, {}); 40591 if (success === 'empty') { 40592 throw new Error(this.s.dt.i18n('stateRestore.emptyError', this.c.i18n.emptyError)); 40593 } 40594 else if (success === 'duplicate') { 40595 throw new Error(this.s.dt.i18n('stateRestore.duplicateError', this.c.i18n.duplicateError)); 40596 } 40597 } 40598 } 40599 else { 40600 throw new Error(this.s.dt.i18n('stateRestore.duplicateError', this.c.i18n.duplicateError)); 40601 } 40602 }; 40603 /** 40604 * Removes all of the states, showing a modal to the user for confirmation 40605 * 40606 * @param removeFunction The action to be taken when the action is confirmed 40607 */ 40608 StateRestoreCollection.prototype.removeAll = function (removeFunction) { 40609 // There are no states to remove so just return 40610 if (this.s.states.length === 0) { 40611 return; 40612 } 40613 var ids = this.s.states.map(function (state) { return state.s.identifier; }); 40614 var replacementString = ids[0]; 40615 if (ids.length > 1) { 40616 replacementString = ids.slice(0, -1).join(', ') + 40617 this.s.dt.i18n('stateRestore.removeJoiner', this.c.i18n.removeJoiner) + 40618 ids.slice(-1); 40619 } 40620 $$1(this.dom.removeContents.children('span')).html(this.s.dt 40621 .i18n('stateRestore.removeConfirm', this.c.i18n.removeConfirm) 40622 .replace(/%s/g, replacementString)); 40623 this._newModal(this.dom.removeTitle, this.s.dt.i18n('stateRestore.removeSubmit', this.c.i18n.removeSubmit), removeFunction, this.dom.removeContents); 40624 }; 40625 /** 40626 * Removes all of the dom elements from the document for the collection and the stored states 40627 */ 40628 StateRestoreCollection.prototype.destroy = function () { 40629 for (var _i = 0, _a = this.s.states; _i < _a.length; _i++) { 40630 var state = _a[_i]; 40631 state.destroy(); 40632 } 40633 Object.values(this.dom).forEach(function (node) { 40634 node.off(); 40635 node.remove(); 40636 }); 40637 this.s.states = []; 40638 this.s.dt.off('.dtsr'); 40639 $$1(this.s.dt.table().node()).off('.dtsr'); 40640 }; 40641 /** 40642 * Identifies active states and updates their button to reflect this. 40643 * 40644 * @returns An array containing objects with the details of currently active states 40645 */ 40646 StateRestoreCollection.prototype.findActive = function () { 40647 // Make sure that the state is up to date 40648 this.s.dt.state.save(); 40649 var currState = this.s.dt.state(); 40650 // Make all of the buttons inactive so that only any that match will be marked as active 40651 var buttons = $$1('button.' + $$1.fn.DataTable.Buttons.defaults.dom.button.className.replace(/ /g, '.')); 40652 // Some of the styling libraries use a tags instead of buttons 40653 if (buttons.length === 0) { 40654 buttons = $$1('a.' + $$1.fn.DataTable.Buttons.defaults.dom.button.className.replace(/ /g, '.')); 40655 } 40656 for (var _i = 0, buttons_1 = buttons; _i < buttons_1.length; _i++) { 40657 var button = buttons_1[_i]; 40658 this.s.dt.button($$1(button).parent()[0]).active(false); 40659 } 40660 var results = []; 40661 // Go through all of the states comparing if their state is the same to the current one 40662 for (var _a = 0, _b = this.s.states; _a < _b.length; _a++) { 40663 var state = _b[_a]; 40664 if (state.compare(currState)) { 40665 results.push({ 40666 data: state.s.savedState, 40667 name: state.s.identifier 40668 }); 40669 // If so, find the corresponding button and mark it as active 40670 for (var _c = 0, buttons_2 = buttons; _c < buttons_2.length; _c++) { 40671 var button = buttons_2[_c]; 40672 if ($$1(button).text() === state.s.identifier) { 40673 this.s.dt.button($$1(button).parent()[0]).active(true); 40674 break; 40675 } 40676 } 40677 } 40678 } 40679 return results; 40680 }; 40681 /** 40682 * Gets a single state that has the identifier matching that which is passed in 40683 * 40684 * @param identifier The value that is used to identify a state 40685 * @returns The state that has been identified or null if no states have been identified 40686 */ 40687 StateRestoreCollection.prototype.getState = function (identifier) { 40688 for (var _i = 0, _a = this.s.states; _i < _a.length; _i++) { 40689 var state = _a[_i]; 40690 if (state.s.identifier === identifier) { 40691 return state; 40692 } 40693 } 40694 return null; 40695 }; 40696 /** 40697 * Gets an array of all of the states 40698 * 40699 * @returns Any states that have been identified 40700 */ 40701 StateRestoreCollection.prototype.getStates = function (ids) { 40702 if (ids === undefined) { 40703 return this.s.states; 40704 } 40705 else { 40706 var states = []; 40707 for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) { 40708 var id = ids_1[_i]; 40709 var found = false; 40710 for (var _a = 0, _b = this.s.states; _a < _b.length; _a++) { 40711 var state = _b[_a]; 40712 if (id === state.s.identifier) { 40713 states.push(state); 40714 found = true; 40715 break; 40716 } 40717 } 40718 if (!found) { 40719 states.push(undefined); 40720 } 40721 } 40722 return states; 40723 } 40724 }; 40725 /** 40726 * Reloads states that are set via datatables config or over ajax 40727 * 40728 * @param preDefined Object containing the predefined states that are to be reintroduced 40729 */ 40730 StateRestoreCollection.prototype._addPreDefined = function (preDefined) { 40731 var _this = this; 40732 // There is a potential issue here if sorting where the string parts of the name are the same, 40733 // only the number differs and there are many states - but this wouldn't be usfeul naming so 40734 // more of a priority to sort alphabetically 40735 var states = Object.keys(preDefined).sort(function (a, b) { return a > b ? 1 : a < b ? -1 : 0; }); 40736 var _loop_1 = function (state) { 40737 for (var i = 0; i < this_1.s.states.length; i++) { 40738 if (this_1.s.states[i].s.identifier === state) { 40739 this_1.s.states.splice(i, 1); 40740 } 40741 } 40742 var that = this_1; 40743 var successCallback = function () { 40744 that.s.states.push(this); 40745 that._collectionRebuild(); 40746 }; 40747 var loadedState = preDefined[state]; 40748 var newState = new StateRestore(this_1.s.dt, $$1.extend(true, {}, this_1.c, loadedState.c !== undefined ? 40749 { saveState: loadedState.c.saveState } : 40750 undefined, true), state, loadedState, true, successCallback); 40751 newState.s.savedState = loadedState; 40752 $$1(this_1.s.dt.table().node()).on('dtsr-modal-inserted', function () { 40753 newState.dom.confirmation.one('dtsr-remove', function () { return _this._removeCallback(newState.s.identifier); }); 40754 newState.dom.confirmation.one('dtsr-rename', function () { return _this._collectionRebuild(); }); 40755 newState.dom.confirmation.one('dtsr-save', function () { return _this._collectionRebuild(); }); 40756 }); 40757 }; 40758 var this_1 = this; 40759 for (var _i = 0, states_1 = states; _i < states_1.length; _i++) { 40760 var state = states_1[_i]; 40761 _loop_1(state); 40762 } 40763 }; 40764 /** 40765 * Rebuilds all of the buttons in the collection of states to make sure that states and text is up to date 40766 */ 40767 StateRestoreCollection.prototype._collectionRebuild = function () { 40768 var button = this.s.dt.button('SaveStateRestore:name'); 40769 var stateButtons = []; 40770 // Need to get the original configuration object, so we can rebuild it 40771 // It might be nested, so need to traverse down the tree 40772 if (button[0]) { 40773 var idxs = button.index().split('-'); 40774 stateButtons = button[0].inst.c.buttons; 40775 for (var i = 0; i < idxs.length; i++) { 40776 if (stateButtons[idxs[i]].buttons) { 40777 stateButtons = stateButtons[idxs[i]].buttons; 40778 } 40779 else { 40780 stateButtons = []; 40781 break; 40782 } 40783 } 40784 } 40785 // remove any states from the previous rebuild - if they are still there they will be added later 40786 for (var i = 0; i < stateButtons.length; i++) { 40787 if (stateButtons[i].extend === 'stateRestore') { 40788 stateButtons.splice(i, 1); 40789 i--; 40790 } 40791 } 40792 if (this.c._createInSaved) { 40793 stateButtons.push('createState'); 40794 } 40795 var emptyText = '<span class="' + this.classes.emptyStates + '">' + 40796 this.s.dt.i18n('stateRestore.emptyStates', this.c.i18n.emptyStates) + 40797 '</span>'; 40798 // If there are no states display an empty message 40799 if (this.s.states.length === 0) { 40800 // Don't want the empty text included more than twice 40801 if (!stateButtons.includes(emptyText)) { 40802 stateButtons.push(emptyText); 40803 } 40804 } 40805 else { 40806 // There are states to add so there shouldn't be any empty text left! 40807 while (stateButtons.includes(emptyText)) { 40808 stateButtons.splice(stateButtons.indexOf(emptyText), 1); 40809 } 40810 // There is a potential issue here if sorting where the string parts of the name are the same, 40811 // only the number differs and there are many states - but this wouldn't be usfeul naming so 40812 // more of a priority to sort alphabetically 40813 this.s.states = this.s.states.sort(function (a, b) { 40814 var aId = a.s.identifier; 40815 var bId = b.s.identifier; 40816 return aId > bId ? 40817 1 : 40818 aId < bId ? 40819 -1 : 40820 0; 40821 }); 40822 // Construct the split property of each button 40823 for (var _i = 0, _a = this.s.states; _i < _a.length; _i++) { 40824 var state = _a[_i]; 40825 var split = Object.assign([], this.c.splitSecondaries); 40826 if (split.includes('updateState') && (!this.c.save || !state.c.save)) { 40827 split.splice(split.indexOf('updateState'), 1); 40828 } 40829 if (split.includes('renameState') && 40830 (!this.c.save || !state.c.save || !this.c.rename || !state.c.rename)) { 40831 split.splice(split.indexOf('renameState'), 1); 40832 } 40833 if (split.includes('removeState') && (!this.c.remove || !state.c.remove)) { 40834 split.splice(split.indexOf('removeState'), 1); 40835 } 40836 if (split.length > 0 && 40837 !split.includes('<h3>' + state.s.identifier + '</h3>')) { 40838 split.unshift('<h3>' + state.s.identifier + '</h3>'); 40839 } 40840 stateButtons.push({ 40841 _stateRestore: state, 40842 attr: { 40843 title: state.s.identifier 40844 }, 40845 config: { 40846 split: split 40847 }, 40848 extend: 'stateRestore', 40849 text: state.s.identifier 40850 }); 40851 } 40852 } 40853 button.collectionRebuild(stateButtons); 40854 // Need to disable the removeAllStates button if there are no states and it is present 40855 var buttons = this.s.dt.buttons(); 40856 for (var _b = 0, buttons_3 = buttons; _b < buttons_3.length; _b++) { 40857 var butt = buttons_3[_b]; 40858 if ($$1(b
40858utt.node).hasClass('dtsr-removeAllStates')) { 40859 if (this.s.states.length === 0) { 40860 this.s.dt.button(butt.node).disable(); 40861 } 40862 else { 40863 this.s.dt.button(butt.node).enable(); 40864 } 40865 } 40866 } 40867 }; 40868 /** 40869 * Displays a modal that is used to get information from the user to create a new state. 40870 * 40871 * @param buttonAction The action that should be taken when the button is pressed 40872 * @param identifier The default identifier for the next new state 40873 */ 40874 StateRestoreCollection.prototype._creationModal = function (buttonAction, identifier, options) { 40875 var _this = this; 40876 this.dom.creation.empty(); 40877 this.dom.creationForm.empty(); 40878 this.dom.nameInputRow.children('input').val(identifier); 40879 this.dom.creationForm.append(this.dom.nameInputRow); 40880 var tableConfig = this.s.dt.settings()[0].oInit; 40881 var togglesToInsert = []; 40882 var toggleDefined = options !== undefined && options.toggle !== undefined; 40883 // Order toggle - check toggle and saving enabled 40884 if (((!toggleDefined || options.toggle.order === undefined) && this.c.toggle.order || 40885 toggleDefined && options.toggle.order) && 40886 this.c.saveState.order && 40887 (tableConfig.ordering === undefined || tableConfig.ordering)) { 40888 togglesToInsert.push(this.dom.orderToggle); 40889 } 40890 // Search toggle - check toggle and saving enabled 40891 if (((!toggleDefined || options.toggle.search === undefined) && this.c.toggle.search || 40892 toggleDefined && options.toggle.search) && 40893 this.c.saveState.search && 40894 (tableConfig.searching === undefined || tableConfig.searching)) { 40895 togglesToInsert.push(this.dom.searchToggle); 40896 } 40897 // Paging toggle - check toggle and saving enabled 40898 if (((!toggleDefined || options.toggle.paging === undefined) && this.c.toggle.paging || 40899 toggleDefined && options.toggle.paging) && 40900 this.c.saveState.paging && 40901 (tableConfig.paging === undefined || tableConfig.paging)) { 40902 togglesToInsert.push(this.dom.pagingToggle); 40903 } 40904 // Page Length toggle - check toggle and saving enabled 40905 if (((!toggleDefined || options.toggle.length === undefined) && this.c.toggle.length || 40906 toggleDefined && options.toggle.length) && 40907 this.c.saveState.length && 40908 (tableConfig.length === undefined || tableConfig.length)) { 40909 togglesToInsert.push(this.dom.lengthToggle); 40910 } 40911 // ColReorder toggle - check toggle and saving enabled 40912 if (this.s.hasColReorder && 40913 ((!toggleDefined || options.toggle.colReorder === undefined) && this.c.toggle.colReorder || 40914 toggleDefined && options.toggle.colReorder) && 40915 this.c.saveState.colReorder) { 40916 togglesToInsert.push(this.dom.colReorderToggle); 40917 } 40918 // Scroller toggle - check toggle and saving enabled 40919 if (this.s.hasScroller && 40920 ((!toggleDefined || options.toggle.scroller === undefined) && this.c.toggle.scroller || 40921 toggleDefined && options.toggle.scroller) && 40922 this.c.saveState.scroller) { 40923 togglesToInsert.push(this.dom.scrollerToggle); 40924 } 40925 // SearchBuilder toggle - check toggle and saving enabled 40926 if (this.s.hasSearchBuilder && 40927 ((!toggleDefined || options.toggle.searchBuilder === undefined) && this.c.toggle.searchBuilder || 40928 toggleDefined && options.toggle.searchBuilder) && 40929 this.c.saveState.searchBuilder) { 40930 togglesToInsert.push(this.dom.searchBuilderToggle); 40931 } 40932 // SearchPanes toggle - check toggle and saving enabled 40933 if (this.s.hasSearchPanes && 40934 ((!toggleDefined || options.toggle.searchPanes === undefined) && this.c.toggle.searchPanes || 40935 toggleDefined && options.toggle.searchPanes) && 40936 this.c.saveState.searchPanes) { 40937 togglesToInsert.push(this.dom.searchPanesToggle); 40938 } 40939 // Select toggle - check toggle and saving enabled 40940 if (this.s.hasSelect && 40941 ((!toggleDefined || options.toggle.select === undefined) && this.c.toggle.select || 40942 toggleDefined && options.toggle.select) && 40943 this.c.saveState.select) { 40944 togglesToInsert.push(this.dom.selectToggle); 40945 } 40946 // Columns toggle - check toggle and saving enabled 40947 if (typeof this.c.toggle.columns === 'boolean' && 40948 ((!toggleDefined || options.toggle.order === undefined) && this.c.toggle.columns || 40949 toggleDefined && options.toggle.order) && 40950 this.c.saveState.columns) { 40951 togglesToInsert.push(this.dom.columnsSearchToggle); 40952 togglesToInsert.push(this.dom.columnsVisibleToggle); 40953 } 40954 else if ((!toggleDefined || options.toggle.columns === undefined) && typeof this.c.toggle.columns !== 'boolean' || 40955 typeof options.toggle.order !== 'boolean') { 40956 if (typeof this.c.saveState.columns !== 'boolean' && this.c.saveState.columns) { 40957 // Column search toggle - check toggle and saving enabled 40958 if (( 40959 // columns.search is defined when passed in 40960 toggleDefined && 40961 options.toggle.columns !== undefined && 40962 typeof options.toggle.columns !== 'boolean' && 40963 options.toggle.columns.search || 40964 // Columns search is not defined when passed in but is
40964in defaults 40965 (!toggleDefined || 40966 options.toggle.columns === undefined || 40967 typeof options.toggle.columns !== 'boolean' && options.toggle.columns.search === undefined) && 40968 typeof this.c.toggle.columns !== 'boolean' && 40969 this.c.toggle.columns.search) && 40970 this.c.saveState.columns.search) { 40971 togglesToInsert.push(this.dom.columnsSearchToggle); 40972 } 40973 // Column visiblity toggle - check toggle and saving enabled 40974 if (( 40975 // columns.visible is defined when passed in 40976 toggleDefined && 40977 options.toggle.columns !== undefined && 40978 typeof options.toggle.columns !== 'boolean' && 40979 options.toggle.columns.visible || 40980 // Columns visible is not defined when passed in but is in defaults 40981 (!toggleDefined || 40982 options.toggle.columns === undefined || 40983 typeof options.toggle.columns !== 'boolean' && options.toggle.columns.visible === undefined) && 40984 typeof this.c.toggle.columns !== 'boolean' && 40985 this.c.toggle.columns.visible) && 40986 this.c.saveState.columns.visible) { 40987 togglesToInsert.push(this.dom.columnsVisibleToggle); 40988 } 40989 } 40990 else if (this.c.saveState.columns) { 40991 togglesToInsert.push(this.dom.columnsSearchToggle); 40992 togglesToInsert.push(this.dom.columnsVisibleToggle); 40993 } 40994 } 40995 // Make sure that the toggles are displayed alphabetically 40996 togglesToInsert.sort(function (a, b) { 40997 var aVal = a.children('label.dtsr-check-label')[0].innerHTML; 40998 var bVal = b.children('label.dtsr-check-label')[0].innerHTML; 40999 if (aVal < bVal) { 41000 return -1; 41001 } 41002 else if (aVal > bVal) { 41003 return 1; 41004 } 41005 else { 41006 return 0; 41007 } 41008 }); 41009 // Append all of the toggles that are to be inserted 41010 for (var _i = 0, togglesToInsert_1 = togglesToInsert; _i < togglesToInsert_1.length; _i++) { 41011 var toggle = togglesToInsert_1[_i]; 41012 this.dom.creationForm.append(toggle); 41013 } 41014 // Insert the toggle label next to the first check box 41015 $$1(this.dom.creationForm.children('div.' + this.classes.checkRow)[0]).prepend(this.dom.toggleLabel); 41016 // Insert the creation modal and the background 41017 this.dom.background.appendTo(this.dom.dtContainer); 41018 this.dom.creation 41019 .append(this.dom.creationTitle) 41020 .append(this.dom.creationForm) 41021 .append(this.dom.createButtonRow) 41022 .appendTo(this.dom.dtContainer); 41023 $$1(this.s.dt.table().node()).trigger('dtsr-modal-inserted'); 41024 var _loop_2 = function (toggle) { 41025 $$1(toggle.children('label:last-child')).on('click', function () { 41026 toggle.children('input').prop('checked', !toggle.children('input').prop('checked')); 41027 }); 41028 }; 41029 // Allow the label to be clicked to toggle the checkbox 41030 for (var _a = 0, togglesToInsert_2 = togglesToInsert; _a < togglesToInsert_2.length; _a++) { 41031 var toggle = togglesToInsert_2[_a]; 41032 _loop_2(toggle); 41033 } 41034 var creationButton = $$1('button.' + this.classes.creationButton.replace(/ /g, '.')); 41035 var inputs = this.dom.creationForm.find('input'); 41036 // If there is an input focus on that 41037 if (inputs.length > 0) { 41038 $$1(inputs[0]).focus(); 41039 } 41040 // Otherwise focus on the confirmation button 41041 else { 41042 creationButton.focus(); 41043 } 41044 var background = $$1('div.' + this.classes.background.replace(/ /g, '.')); 41045 var keyupFunction = function (e) { 41046 if (e.key === 'Enter') { 41047 creationButton.click(); 41048 } 41049 else if (e.key === 'Escape') { 41050 background.click(); 41051 } 41052 }; 41053 if (this.c.modalCloseButton) { 41054 this.dom.creation.append(this.dom.closeButton); 41055 this.dom.closeButton.on('click', function () { return background.click(); }); 41056 } 41057 creationButton.on('click', function () { 41058 // Get the values of the checkBoxes 41059 var saveState = { 41060 colReorder: _this.dom.colReorderToggle.children('input').is(':checked'), 41061 columns: { 41062 search: _this.dom.columnsSearchToggle.children('input').is(':checked'), 41063 visible: _this.dom.columnsVisibleToggle.children('input').is(':checked') 41064 }, 41065 length: _this.dom.lengthToggle.children('input').is(':checked'), 41066 order: _this.dom.orderToggle.children('input').is(':checked'), 41067 paging: _this.dom.pagingToggle.children('input').is(':checked'), 41068 scroller: _this.dom.scrollerToggle.children('input').is(':checked'), 41069 search: _this.dom.searchToggle.children('input').is(':checked'), 41070 searchBuilder: _this.dom.searchBuilderToggle.children('input').is(':checked'), 41071 searchPanes: _this.dom.searchPanesToggle.children('input').is(':checked'), 41072 select: _this.dom.selectToggle.children('input').is(':checked') 41073 }; 41074 // Call the buttons functionality passing in the identifier and what should be saved 41075 var success = buttonAction($$1('input.' + _this.classes.nameInput.replace(/ /g, '.')).val(), { saveState: saveState }); 41076 if (success === true) { 41077 // Remove the dom elements as operation has completed 41078 _this.dom.background.remove(); 41079 _this.dom.creation.remove(); 41080 // Unbind the keyup function - don't want it to run unnecessarily on every keypress that occurs 41081 $$1(document).unbind('keyup', keyupFunction); 41082 } 41083 else { 41084 _this.dom.creation.children('.' + _this.classes.modalError).remove(); 41085 _this.dom.creation.append(_this.dom[success + 'Error']); 41086 } 41087 }); 41088 background.one('click', function () { 41089 // Remove the dome elements as operation has been cancelled 41090 _this.dom.background.remove(); 41091 _this.dom.creation.remove(); 41092 // Unbind the keyup function - don't want it to run unnecessarily on every keypress that occurs 41093 $$1(document).unbind('keyup', keyupFunction); 41094 // Rebuild the collection to ensure that the latest changes are present 41095 _this._collectionRebuild(); 41096 }); 41097 // Have to listen to the keyup event as `escape` doesn't trigger keypress 41098 $$1(document).on('keyup', keyupFunction); 41099 // Need to save the state before the focus is lost when the modal is interacted with 41100 this.s.dt.state.save(); 41101 }; 41102 /** 41103 * This callback is called when a state is removed.
41104 * This removes the state from storage and also strips it's button from the container 41105 * 41106 * @param identifier The value that is used to identify a state 41107 */ 41108 StateRestoreCollection.prototype._removeCallback = function (identifier) { 41109 for (var i = 0; i < this.s.states.length; i++) { 41110 if (this.s.states[i].s.identifier === identifier) { 41111 this.s.states.splice(i, 1); 41112 i--; 41113 } 41114 } 41115 this._collectionRebuild(); 41116 return true; 41117 }; 41118 /** 41119 * Creates a new confirmation modal for the user to approve an action 41120 * 41121 * @param title The title that is to be displayed at the top of the modal 41122 * @param buttonText The text that is to be displayed in the confirmation button of the modal 41123 * @param buttonAction The action that should be taken when the confirmation button is pressed 41124 * @param modalContents The contents for the main body of the modal 41125 */ 41126 StateRestoreCollection.prototype._newModal = function (title, buttonText, buttonAction, modalContents) { 41127 var _this = this; 41128 this.dom.background.appendTo(this.dom.dtContainer); 41129 this.dom.confirmationTitleRow.empty().append(title); 41130 var confirmationButton = $$1('<button class="' + this.classes.confirmationButton + ' ' + this.classes.dtButton + '">' + 41131 buttonText + 41132 '</button>'); 41133 this.dom.confirmation 41134 .empty() 41135 .append(this.dom.confirmationTitleRow) 41136 .append(modalContents) 41137 .append($$1('<div class="' + this.classes.confirmationButtons + '"></div>') 41138 .append(confirmationButton)) 41139 .appendTo(this.dom.dtContainer); 41140 $$1(this.s.dt.table().node()).trigger('dtsr-modal-inserted'); 41141 var inputs = modalContents.children('input'); 41142 // If there is an input focus on that 41143 if (inputs.length > 0) { 41144 $$1(inputs[0]).focus(); 41145 } 41146 // Otherwise focus on the confirmation button 41147 else { 41148 confirmationButton.focus(); 41149 } 41150 var background = $$1('div.' + this.classes.background.replace(/ /g, '.')); 41151 var keyupFunction = function (e) { 41152 // If enter same action as pressing the button 41153 if (e.key === 'Enter') { 41154 confirmationButton.click(); 41155 } 41156 // If escape close modal 41157 else if (e.key === 'Escape') { 41158 background.click(); 41159 } 41160 }; 41161 // When the button is clicked, call the appropriate action, 41162 // remove the background and modal from the screen and unbind the keyup event. 41163 confirmationButton.on('click', function () { 41164 var success = buttonAction(true); 41165 if (success === true) { 41166 _this.dom.background.remove(); 41167 _this.dom.confirmation.remove(); 41168 $$1(document).unbind('keyup', keyupFunction); 41169 confirmationButton.off('click'); 41170 } 41171 else { 41172 _this.dom.confirmation.children('.' + _this.classes.modalError).remove(); 41173 _this.dom.confirmation.append(_this.dom[success + 'Error']); 41174 } 41175 }); 41176 this.dom.confirmation.on('click', function (e) { 41177 e.stopPropagation(); 41178 }); 41179 // When the button is clicked, remove the background and modal from the screen and unbind the keyup event. 41180 background.one('click', function () { 41181 _this.dom.background.remove(); 41182 _this.dom.confirmation.remove(); 41183 $$1(document).unbind('keyup', keyupFunction); 41184 }); 41185 $$1(document).on('keyup', keyupFunction); 41186 }; 41187 /** 41188 * Private method that checks for previously created states on initialisation 41189 */ 41190 StateRestoreCollection.prototype._searchForStates = function () { 41191 var _this = this; 41192 var keys = Object.keys(localStorage); 41193 var _loop_3 = function (key) { 41194 // eslint-disable-next-line no-useless-escape 41195 if (key.match(new RegExp('^DataTables_stateRestore_.*_' + location.pathname.replace(/\//g, '/') + '$')) || 41196 key.match(new RegExp('^DataTables_stateRestore_.*_' + location.pathname.replace(/\//g, '/') + 41197 '_' + this_2.s.dt.table().node().id + '$'))) { 41198 var loadedState_1 = JSON.parse(localStorage.getItem(key)); 41199 if (loadedState_1.stateRestore.isPreDefined || 41200 (loadedState_1.stateRestore.tableId && 41201 loadedState_1.stateRestore.tableId !== this_2.s.dt.table().node().id)) { 41202 return "continue"; 41203 } 41204 var that_1 = this_2; 41205 var successCallback = function () { 41206 this.s.savedState = loadedState_1; 41207 that_1.s.states.push(this); 41208 that_1._collectionRebuild(); 41209 }; 41210 var newState_1 = new StateRestore(this_2.s.dt, $$1.extend(true, {}, this_2.c, { saveState: loadedState_1.c.saveState }), loadedState_1.stateRestore.state, loadedState_1, false, successCallback); 41211 $$1(this_2.s.dt.table().node()).on('dtsr-modal-inserted', function () { 41212 newState_1.dom.confirmation.one('dtsr-remove', function () { return _this._removeCallback(newState_1.s.identifier); }); 41213 newState_1.dom.confirmation.one('dtsr-rename', function () { return _this._collectionRebuild(); }); 41214 newState_1.dom.confirmation.one('dtsr-save', function () { return _this._collectionRebuild(); }); 41215 }); 41216 } 41217 }; 41218 var this_2 = this; 41219 for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { 41220 var key = keys_1[_i]; 41221 _loop_3(key); 41222 } 41223 }; 41224 StateRestoreCollection.version = '1.0.0'; 41225 StateRestoreCollection.classes = { 41226 background: 'dtsr-background', 41227 checkBox: 'dtsr-check-box', 41228 checkLabel: 'dtsr-check-label', 41229 checkRow: 'dtsr-check-row', 41230 closeButton: 'dtsr-popover-close', 41231 colReorderToggle: 'dtsr-colReorder-toggle', 41232 columnsSearchToggle: 'dtsr-columns-search-toggle', 41233 columnsVisibleToggle: 'dtsr-columns-visible-toggle', 41234 confirmation: 'dtsr-confirmation', 41235 confirmationButton: 'dtsr-confirmation-button', 41236 confirmationButtons: 'dtsr-confirmation-buttons', 41237 confirmationMessage: 'dtsr-confirmation-message dtsr-name-label', 41238 confirmationText: 'dtsr-confirmation-text', 41239 confirmationTitle: 'dtsr-confirmation-title', 41240 confirmationTitleRow: 'dtsr-confirmation-title-row', 41241 creation: 'dtsr-creation', 41242 creationButton: 'dtsr-creation-button', 41243 creationForm: 'dtsr-creation-form',
41244 creationText: 'dtsr-creation-text', 41245 creationTitle: 'dtsr-creation-title', 41246 dtButton: 'dt-button', 41247 emptyStates: 'dtsr-emptyStates', 41248 formRow: 'dtsr-form-row', 41249 leftSide: 'dtsr-left', 41250 lengthToggle: 'dtsr-length-toggle', 41251 modalError: 'dtsr-modal-error', 41252 modalFoot: 'dtsr-modal-foot', 41253 nameInput: 'dtsr-name-input', 41254 nameLabel: 'dtsr-name-label', 41255 orderToggle: 'dtsr-order-toggle', 41256 pagingToggle: 'dtsr-paging-toggle', 41257 rightSide: 'dtsr-right', 41258 scrollerToggle: 'dtsr-scroller-toggle', 41259 searchBuilderToggle: 'dtsr-searchBuilder-toggle', 41260 searchPanesToggle: 'dtsr-searchPanes-toggle', 41261 searchToggle: 'dtsr-search-toggle', 41262 selectToggle: 'dtsr-select-toggle', 41263 toggleLabel: 'dtsr-toggle-title' 41264 }; 41265 StateRestoreCollection.defaults = { 41266 _createInSaved: false, 41267 ajax: false, 41268 create: true, 41269 creationModal: false, 41270 i18n: { 41271 creationModal: { 41272 button: 'Create', 41273 colReorder: 'Column Order', 41274 columns: { 41275 search: 'Column Search', 41276 visible: 'Column Visibility' 41277 }, 41278 length: 'Page Length', 41279 name: 'Name:', 41280 order: 'Sorting', 41281 paging: 'Paging', 41282 scroller: 'Scroll Position', 41283 search: 'Search', 41284 searchBuilder: 'SearchBuilder', 41285 searchPanes: 'SearchPanes', 41286 select: 'Select', 41287 title: 'Create New State', 41288 toggleLabel: 'Includes:' 41289 }, 41290 duplicateError: 'A state with this name already exists.', 41291 emptyError: 'Name cannot be empty.', 41292 emptyStates: 'No saved states', 41293 removeConfirm: 'Are you sure you want to remove %s?', 41294 removeError: 'Failed to remove state.', 41295 removeJoiner: ' and ', 41296 removeSubmit: 'Remove', 41297 removeTitle: 'Remove State', 41298 renameButton: 'Rename', 41299 renameLabel: 'New Name for %s:', 41300 renameTitle: 'Rename State' 41301 }, 41302 modalCloseButton: true, 41303 preDefined: {}, 41304 remove: true, 41305 rename: true, 41306 save: true, 41307 saveState: { 41308 colReorder: true, 41309 columns: { 41310 search: true, 41311 visible: true 41312 }, 41313 length: true, 41314 order: true, 41315 paging: true, 41316 scroller: true, 41317 search: true, 41318 searchBuilder: true, 41319 searchPanes: true, 41320 select: true 41321 }, 41322 splitSecondaries: [ 41323 'updateState', 41324 'renameState', 41325 'removeState' 41326 ], 41327 toggle: { 41328 colReorder: false, 41329 columns: { 41330 search: false, 41331 visible: false 41332 }, 41333 length: false, 41334 order: false, 41335 paging: false, 41336 scroller: false, 41337 search: false, 41338 searchBuilder: false, 41339 searchPanes: false, 41340 select: false 41341 } 41342 }; 41343 return StateRestoreCollection; 41344 }()); 41345 41346 /*! StateRestore 1.2.2 41347 * © SpryMedia Ltd - datatables.net/license 41348 */ 41349 setJQuery$1($); 41350 setJQuery($); 41351 $.fn.dataTable.StateRestore = StateRestore; 41352 $.fn.DataTable.StateRestore = StateRestore; 41353 $.fn.dataTable.StateRestoreCollection = StateRestoreCollection; 41354 $.fn.DataTable.StateRestoreCollection = StateRestoreCollection; 41355 var apiRegister = DataTable.Api.register; 41356 apiRegister('stateRestore()', function () { 41357 return this; 41358 });
41359 apiRegister('stateRestore.state()', function (identifier) { 41360 var ctx = this.context[0]; 41361 if (!ctx._stateRestore) { 41362 var api = DataTable.Api(ctx); 41363 var src = new DataTable.StateRestoreCollection(api, {}); 41364 _stateRegen(api, src); 41365 } 41366 this[0] = ctx._stateRestore.getState(identifier); 41367 return this; 41368 }); 41369 apiRegister('stateRestore.state.add()', function (identifier, options) { 41370 var ctx = this.context[0]; 41371 if (!ctx._stateRestore) { 41372 var api = DataTable.Api(ctx); 41373 var src = new DataTable.StateRestoreCollection(api, {}); 41374 _stateRegen(api, src); 41375 } 41376 if (!ctx._stateRestore.c.create) { 41377 return this; 41378 } 41379 if (ctx._stateRestore.addState) { 41380 var states = ctx._stateRestore.s.states; 41381 var ids = []; 41382 for (var _i = 0, states_1 = states; _i < states_1.length; _i++) { 41383 var intState = states_1[_i]; 41384 ids.push(intState.s.identifier); 41385 } 41386 ctx._stateRestore.addState(identifier, ids, options); 41387 return this; 41388 } 41389 }); 41390 apiRegister('stateRestore.states()', function (ids) { 41391 var ctx = this.context[0]; 41392 if (!ctx._stateRestore) { 41393 var api = DataTable.Api(ctx); 41394 var src = new DataTable.StateRestoreCollection(api, {}); 41395 _stateRegen(api, src); 41396 } 41397 this.length = 0; 41398 this.push.apply(this, ctx._stateRestore.getStates(ids)); 41399 return this; 41400 }); 41401 apiRegister('stateRestore.state().save()', function () { 41402 var ctx = this[0]; 41403 // Check if saving states is allowed 41404 if (ctx.c.save) { 41405 ctx.save(); 41406 } 41407 return this; 41408 }); 41409 apiRegister('stateRestore.state().rename()', function (newIdentifier) { 41410 var ctx = this.context[0]; 41411 var state = this[0]; 41412 // Check if renaming states is allowed 41413 if (state.c.save) { 41414 var states = ctx._stateRestore.s.states; 41415 var ids = []; 41416 for (var _i = 0, states_2 = states; _i < states_2.length; _i++) { 41417 var intState = states_2[_i]; 41418 ids.push(intState.s.identifier); 41419 } 41420 state.rename(newIdentifier, ids); 41421 } 41422 return this; 41423 }); 41424 apiRegister('stateRestore.state().load()', function () { 41425 var ctx = this[0]; 41426 ctx.load(); 41427 return this; 41428 }); 41429 apiRegister('stateRestore.state().remove()', function (skipModal) { 41430 var ctx = this[0]; 41431 // Check if removal of states is allowed 41432 if (ctx.c.remove) { 41433 ctx.remove(skipModal); 41434 } 41435 return this; 41436 }); 41437 apiRegister('stateRestore.states().remove()', function (skipModal) { 41438 var _this = this; 41439 var removeAllCallBack = function (skipModalIn) { 41440 var success = true; 41441 var that = _this.toArray(); 41442 while (that.length > 0) { 41443 var set = that[0]; 41444 if (set !== undefined && set.c.remove) { 41445 var tempSuccess = set.remove(skipModalIn); 41446 if (tempSuccess !== true) { 41447 success = tempSuccess; 41448 } 41449 else { 41450 that.splice(0, 1); 41451 } 41452 } 41453 else { 41454 break; 41455 } 41456 } 41457 return success; 41458 }; 41459 if (this.context[0]._stateRestore && this.context[0]._stateRestore.c.remove) { 41460 if (skipModal) { 41461 removeAllCallBack(skipModal); 41462 } 41463 else { 41464 this.context[0]._stateRestore.removeAll(removeAllCallBack); 41465 } 41466 } 41467 return this; 41468 }); 41469 apiRegister('stateRestore.activeStates()', function () { 41470 var ctx = this.context[0]; 41471 this.length = 0; 41472 if (!ctx._stateRestore) { 41473 var api = DataTable.Api(ctx); 41474 var src = new DataTable.StateRestoreCollection(api, {}); 41475 _stateRegen(api, src); 41476 } 41477 if (ctx._stateRestore) { 41478 this.push.apply(this, ctx._stateRestore.findActive()); 41479 } 41480 return this; 41481 });
41482 DataTable.ext.buttons.stateRestore = { 41483 action: function (e, dt, node, config) { 41484 config._stateRestore.load(); 41485 node.blur(); 41486 }, 41487 config: { 41488 split: ['updateState', 'renameState', 'removeState'] 41489 }, 41490 text: function (dt) { 41491 return dt.i18n('buttons.stateRestore', 'State %d', dt.stateRestore.states()[0].length + 1); 41492 } 41493 }; 41494 DataTable.ext.buttons.updateState = { 41495 action: function (e, dt, node, config) { 41496 $('div.dt-button-background').click(); 41497 config.parent._stateRestore.save(); 41498 }, 41499 text: function (dt) { 41500 return dt.i18n('buttons.updateState', 'Update'); 41501 } 41502 }; 41503 DataTable.ext.buttons.savedStates = { 41504 buttons: [], 41505 extend: 'collection', 41506 init: function (dt, node, config) { 41507 dt.on('stateRestore-change', function () { 41508 dt.button(node).text(dt.i18n('buttons.savedStates', 'Saved States', dt.stateRestore.states().length)); 41509 }); 41510 if (dt.settings()[0]._stateRestore === undefined) { 41511 _buttonInit(dt, config); 41512 } 41513 }, 41514 name: 'SaveStateRestore', 41515 text: function (dt) { 41516 return dt.i18n('buttons.savedStates', 'Saved States', 0); 41517 } 41518 }; 41519 DataTable.ext.buttons.savedStatesCreate = { 41520 buttons: [], 41521 extend: 'collection', 41522 init: function (dt, node, config) { 41523 dt.on('stateRestore-change', function () { 41524 dt.button(node).text(dt.i18n('buttons.savedStates', 'Saved States', dt.stateRestore.states().length)); 41525 }); 41526 if (dt.settings()[0]._stateRestore === undefined) { 41527 if (config.config === undefined) { 41528 config.config = {}; 41529 } 41530 config.config._createInSaved = true; 41531 _buttonInit(dt, config); 41532 } 41533 }, 41534 name: 'SaveStateRestore', 41535 text: function (dt) { 41536 return dt.i18n('buttons.savedStates', 'Saved States', 0); 41537 } 41538 }; 41539 DataTable.ext.buttons.createState = { 41540 action: function (e, dt, node, config) { 41541 e.stopPropagation(); 41542 var stateRestoreOpts = dt.settings()[0]._stateRestore.c; 41543 var language = dt.settings()[0].oLanguage; 41544 // If creation/saving is not allowed then return 41545 if (!stateRestoreOpts.create || !stateRestoreOpts.save) { 41546 return; 41547 } 41548 var prevStates = dt.stateRestore.states().toArray(); 41549 // Create a replacement regex based on the i18n values 41550 var defaultString = language.buttons !== undefined && language.buttons.stateRestore !== undefined ? 41551 language.buttons.stateRestore : 41552 'State '; 41553 var replaceRegex; 41554 if (defaultString.indexOf('%d') === defaultString.length - 3) { 41555 replaceRegex = new RegExp(defaultString.replace(/%d/g, '')); 41556 } 41557 else { 41558 var splitString = defaultString.split('%d'); 41559 replaceRegex = []; 41560 for (var _i = 0, splitString_1 = splitString; _i < splitString_1.length; _i++) { 41561 var split = splitString_1[_i]; 41562 replaceRegex.push(new RegExp(split)); 41563 } 41564 } 41565 var getId = function (identifier) { 41566 var id; 41567 if (Array.isArray(replaceRegex)) { 41568 id = identifier; 41569 for (var _i = 0, replaceRegex_1 = replaceRegex; _i < replaceRegex_1.length; _i++) { 41570 var reg = replaceRegex_1[_i]; 41571 id = id.replace(reg, ''); 41572 } 41573 } 41574 else { 41575 id = identifier.replace(replaceRegex, ''); 41576 } 41577 // If the id after replacement is not a number, or the length is the same as before, 41578 // it has been customised so return 0 41579 if (isNaN(+id) || id.length === identifier) { 41580 return 0; 41581 } 41582 // Otherwise return the number that has been assigned previously 41583 else { 41584 return +id; 41585 } 41586 }; 41587 // Extract the numbers from the identifiers that use the standard naming convention 41588 var identifiers = prevStates 41589 .map(function (state) { return getId(state.s.identifier); }) 41590 .sort(function (a, b) { return +a < +b ? 41591 1 : 41592 +a > +b ? 41593 -1 : 41594 0; }); 41595 var lastNumber = identifiers[0]; 41596 dt.stateRestore.state.add(dt.i18n('buttons.stateRestore', 'State %d', lastNumber !== undefined ? lastNumber + 1 : 1), config.config); 41597 var states = dt.stateRestore.states().sort(function (a, b) { 41598 var aId = +getId(a.s.identifier); 41599 var bId = +getId(b.s.identifier); 41600 return aId > bId ? 41601 1 : 41602 aId < bId ? 41603 -1 : 41604 0; 41605 }); 41606 var button = dt.button('SaveStateRestore:name'); 41607 var stateButtons = button[0] !== undefined && button[0].inst.c.buttons[0].buttons !== undefined ? 41608 button[0].inst.c.buttons[0].buttons : 41609 []; 41610 // remove any states from the previous rebuild - if they are still there they will be added later 41611 for (var i = 0; i < stateButtons.length; i++) { 41612 if (stateButtons[i].extend === 'stateRestore') { 41613 stateButtons.splice(i, 1); 41614 i--; 41615 } 41616 } 41617 if (stateRestoreOpts._createInSaved) { 41618 stateButtons.push('createState'); 41619 stateButtons.push(''); 41620 } 41621 for (var _a = 0, states_3 = states; _a < states_3.length; _a++) { 41622 var state = states_3[_a]; 41623 var split = Object.assign([], stateRestoreOpts.splitSecondaries); 41624 if (split.includes('updateState') && !stateRestoreOpts.save) { 41625 split.splice(split.indexOf('updateState'), 1); 41626 } 41627 if (split.includes('renameState') && 41628 (!stateRestoreOpts.save || !stateRestoreOpts.rename)) { 41629 split.splice(split.indexOf('renameState'), 1); 41630 } 41631 if (split.includes('removeState') && !stateRestoreOpts.remove) { 41632 split.splice(split.indexOf('removeState'), 1); 41633 } 41634 if (split.length > 0 && 41635 !split.includes('<h3>' + state.s.identifier + '</h3>')) { 41636 split.unshift('<h3>' + state.s.identifier + '</h3>'); 41637 } 41638 stateButtons.push({ 41639 _stateRestore: state, 41640 attr: { 41641 title: state.s.identifier 41642 }, 41643 config: { 41644 split: split 41645 }, 41646 extend: 'stateRestore', 41647 text: state.s.identifier 41648 }); 41649 }
41650 dt.button('SaveStateRestore:name').collectionRebuild(stateButtons); 41651 node.blur(); 41652 // Need to disable the removeAllStates button if there are no states and it is present 41653 var buttons = dt.buttons(); 41654 for (var _b = 0, buttons_1 = buttons; _b < buttons_1.length; _b++) { 41655 var butt = buttons_1[_b]; 41656 if ($(butt.node).hasClass('dtsr-removeAllStates')) { 41657 if (states.length === 0) { 41658 dt.button(butt.node).disable(); 41659 } 41660 else { 41661 dt.button(butt.node).enable(); 41662 } 41663 } 41664 } 41665 }, 41666 init: function (dt, node, config) { 41667 if (dt.settings()[0]._stateRestore === undefined && dt.button('SaveStateRestore:name').length > 1) { 41668 _buttonInit(dt, config); 41669 } 41670 }, 41671 text: function (dt) { 41672 return dt.i18n('buttons.createState', 'Create State'); 41673 } 41674 }; 41675 DataTable.ext.buttons.removeState = { 41676 action: function (e, dt, node, config) { 41677 config.parent._stateRestore.remove(); 41678 node.blur(); 41679 }, 41680 text: function (dt) { 41681 return dt.i18n('buttons.removeState', 'Remove'); 41682 } 41683 }; 41684 DataTable.ext.buttons.removeAllStates = { 41685 action: function (e, dt, node) { 41686 dt.stateRestore.states().remove(true); 41687 node.blur(); 41688 }, 41689 className: 'dt-button dtsr-removeAllStates', 41690 init: function (dt, node) { 41691 if (!dt.settings()[0]._stateRestore || dt.stateRestore.states().length === 0) { 41692 $(node).addClass('disabled'); 41693 } 41694 }, 41695 text: function (dt) { 41696 return dt.i18n('buttons.removeAllStates', 'Remove All States'); 41697 } 41698 }; 41699 DataTable.ext.buttons.renameState = { 41700 action: function (e, dt, node, config) { 41701 var states = dt.settings()[0]._stateRestore.s.states; 41702 var ids = []; 41703 for (var _i = 0, states_4 = states; _i < states_4.length; _i++) { 41704 var state = states_4[_i]; 41705 ids.push(state.s.identifier); 41706 } 41707 config.parent._stateRestore.rename(undefined, ids); 41708 node.blur(); 41709 }, 41710 text: function (dt) { 41711 return dt.i18n('buttons.renameState', 'Rename'); 41712 } 41713 }; 41714 function _init(settings, options) { 41715 if (options === void 0) { options = null; } 41716 var api = new DataTable.Api(settings); 41717 var opts = options 41718 ? options 41719 : api.init().stateRestore || DataTable.defaults.stateRestore; 41720 var stateRestore = new StateRestoreCollection(api, opts); 41721 _stateRegen(api, stateRestore); 41722 return stateRestore; 41723 } 41724 /** 41725 * Initialisation function if initialising using a button 41726 * 41727 * @param dt The datatables instance 41728 * @param config the config for the button 41729 */ 41730 function _buttonInit(dt, config) { 41731 var SRC = new DataTable.StateRestoreCollection(dt, config.config); 41732 _stateRegen(dt, SRC); 41733 } 41734 function _stateRegen(dt, src) { 41735 var states = dt.stateRestore.states(); 41736 var button = dt.button('SaveStateRestore:name'); 41737 var stateButtons = []; 41738 // Need to get the original configuration object, so we can rebuild it 41739 // It might be nested, so need to traverse down the tree 41740 if (button[0]) { 41741 var idxs = button.index().split('-'); 41742 stateButtons = button[0].inst.c.buttons; 41743 for (var i = 0; i < idxs.length; i++) { 41744 if (stateButtons[idxs[i]].buttons) { 41745 stateButtons = stateButtons[idxs[i]].buttons; 41746 } 41747 else { 41748 stateButtons = []; 41749 break; 41750 } 41751 } 41752 } 41753 var stateRestoreOpts = dt.settings()[0]._stateRestore.c; 41754 // remove any states from the previous rebuild - if they are still there they will be added later 41755 for (var i = 0; i < stateButtons.length; i++) { 41756 if (stateButtons[i].extend === 'stateRestore') { 41757 stateButtons.splice(i, 1); 41758 i--; 41759 } 41760 } 41761 if (stateRestoreOpts._createInSaved) { 41762 stateButtons.push('createState'); 41763 } 41764 if (states === undefined || states.length === 0) { 41765 stateButtons.push('<span class="' + src.classes.emptyStates + '">' + 41766 dt.i18n('stateRestore.emptyStates', src.c.i18n.emptyStates) + 41767 '</span>'); 41768 } 41769 else { 41770 for (var _i = 0, states_5 = states; _i < states_5.length; _i++) { 41771 var state = states_5[_i]; 41772 var split = Object.assign([], stateRestoreOpts.splitSecondaries); 41773 if (split.includes('updateState') && !stateRestoreOpts.save) { 41774 split.splice(split.indexOf('updateState'), 1); 41775 } 41776 if (split.includes('renameState') && 41777 (!stateRestoreOpts.save || !stateRestoreOpts.rename)) { 41778 split.splice(split.indexOf('renameState'), 1); 41779 } 41780 if (split.includes('removeState') && !stateRestoreOpts.remove) { 41781 split.splice(split.indexOf('removeState'), 1); 41782 } 41783 if (split.length > 0 && 41784 !split.includes('<h3>' + state.s.identifier + '</h3>')) { 41785 split.unshift('<h3>' + state.s.identifier + '</h3>'); 41786 } 41787 stateButtons.push({ 41788 _stateRestore: state, 41789 attr: { 41790 title: state.s.identifier 41791 }, 41792 config: { 41793 split: split 41794 }, 41795 extend: 'stateRestore', 41796 text: state.s.identifier 41797 }); 41798 } 41799 }
41800 dt.button('SaveStateRestore:name').collectionRebuild(stateButtons); 41801 // Need to disable the removeAllStates button if there are no states and it is present 41802 var buttons = dt.buttons(); 41803 for (var _a = 0, buttons_2 = buttons; _a < buttons_2.length; _a++) { 41804 var butt = buttons_2[_a]; 41805 if ($(butt.node).hasClass('dtsr-removeAllStates')) { 41806 if (states.length === 0) { 41807 dt.button(butt.node).disable(); 41808 } 41809 else { 41810 dt.button(butt.node).enable(); 41811 } 41812 } 41813 } 41814 } 41815 // Attach a listener to the document which listens for DataTables initialisation 41816 // events so we can automatically initialise 41817 $(document).on('preInit.dt.dtsr', function (e, settings) { 41818 if (e.namespace !== 'dt') { 41819 return; 41820 } 41821 if (settings.oInit.stateRestore || 41822 DataTable.defaults.stateRestore) { 41823 if (!settings._stateRestore) { 41824 _init(settings, null); 41825 } 41826 } 41827 }); 41828 41829})(); 41830 41831 41832return DataTable; 41833})); 41834 41835 41836/*! Bootstrap integration for DataTables' StateRestore 41837 * © SpryMedia Ltd - datatables.net/license 41838 */ 41839 41840(function( factory ){ 41841 if ( typeof define === 'function' && define.amd ) { 41842 // AMD 41843 define( ['jquery', 'datatables.net-dt', 'datatables.net-staterestore'], function ( $ ) { 41844 return factory( $, window, document ); 41845 } ); 41846 } 41847 else if ( typeof exports === 'object' ) { 41848 // CommonJS 41849 var jq = require('jquery'); 41850 var cjsRequires = function (root, $) { 41851 if ( ! $.fn.dataTable ) { 41852 require('datatables.net-dt')(root, $); 41853 } 41854 41855 if ( ! $.fn.dataTable.StateRestore ) { 41856 require('datatables.net-staterestore')(root, $); 41857 } 41858 }; 41859 41860 if (typeof window !== 'undefined') { 41861 module.exports = function (root, $) { 41862 if ( ! root ) { 41863 // CommonJS environments without a window global must pass a 41864 // root. This will give an error otherwise 41865 root = window; 41866 } 41867 41868 if ( ! $ ) { 41869 $ = jq( root ); 41870 } 41871 41872 cjsRequires( root, $ ); 41873 return factory( $, root, root.document ); 41874 }; 41875 } 41876 else { 41877 cjsRequires( window, jq ); 41878 module.exports = factory( jq, window, window.document ); 41879 } 41880 } 41881 else { 41882 // Browser 41883 factory( jQuery, window, document ); 41884 } 41885}(function( $, window, document, undefined ) { 41886'use strict'; 41887var DataTable = $.fn.dataTable; 41888 41889 41890 41891 41892return DataTable; 41893})); 41894 41895
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.