PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.5.2
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.5.2
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / asset / libs / DataTables / datatables.js

datatables.js in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.5.2, at asset/libs/DataTables/datatables.js

18,836 lines 491.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * This combined file was created by the DataTables downloader builder:
3 * https://datatables.net/download
4 *
5 * To rebuild or modify this file with the latest versions of the included
6 * software please visit:
7 * https://datatables.net/download/#dt/dt-2.2.2/b-3.2.2/r-3.0.4
8 *
9 * Included libraries:
10 * DataTables 2.2.2, Buttons 3.2.2, Responsive 3.0.4
11 */
12
13 /*! DataTables 2.2.2
14 * © SpryMedia Ltd - datatables.net/license
15 */
16
17 (function( factory ) {
18 "use strict";
19
20 if ( typeof define === 'function' && define.amd ) {
21 // AMD
22 define( ['jquery'], function ( $ ) {
23 return factory( $, window, document );
24 } );
25 }
26 else if ( typeof exports === 'object' ) {
27 // CommonJS
28 // jQuery's factory checks for a global window - if it isn't present then it
29 // returns a factory function that expects the window object
30 var jq = require('jquery');
31
32 if (typeof window === 'undefined') {
33 module.exports = function (root, $) {
34 if ( ! root ) {
35 // CommonJS environments without a window global must pass a
36 // root. This will give an error otherwise
37 root = window;
38 }
39
40 if ( ! $ ) {
41 $ = jq( root );
42 }
43
44 return factory( $, root, root.document );
45 };
46 }
47 else {
48 module.exports = factory( jq, window, window.document );
49 }
50 }
51 else {
52 // Browser
53 window.DataTable = factory( jQuery, window, document );
54 }
55 }(function( $, window, document ) {
56 "use strict";
57
58
59 var DataTable = function ( selector, options )
60 {
61 // Check if called with a window or jQuery object for DOM less applications
62 // This is for backwards compatibility
63 if (DataTable.factory(selector, options)) {
64 return DataTable;
65 }
66
67 // When creating with `new`, create a new DataTable, returning the API instance
68 if (this instanceof DataTable) {
69 return $(selector).DataTable(options);
70 }
71 else {
72 // Argument switching
73 options = selector;
74 }
75
76 var _that = this;
77 var emptyInit = options === undefined;
78 var len = this.length;
79
80 if ( emptyInit ) {
81 options = {};
82 }
83
84 // Method to get DT API instance from jQuery object
85 this.api = function ()
86 {
87 return new _Api( this );
88 };
89
90 this.each(function() {
91 // For each initialisation we want to give it a clean initialisation
92 // object that can be bashed around
93 var o = {};
94 var oInit = len > 1 ? // optimisation for single table case
95 _fnExtend( o, options, true ) :
96 options;
97
98
99 var i=0, iLen;
100 var sId = this.getAttribute( 'id' );
101 var defaults = DataTable.defaults;
102 var $this = $(this);
103
104
105 /* Sanity check */
106 if ( this.nodeName.toLowerCase() != 'table' )
107 {
108 _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
109 return;
110 }
111
112 $(this).trigger( 'options.dt', oInit );
113
114 /* Backwards compatibility for the defaults */
115 _fnCompatOpts( defaults );
116 _fnCompatCols( defaults.column );
117
118 /* Convert the camel-case defaults to Hungarian */
119 _fnCamelToHungarian( defaults, defaults, true );
120 _fnCamelToHungarian( defaults.column, defaults.column, true );
121
122 /* Setting up the initialisation object */
123 _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true );
124
125
126
127 /* Check to see if we are re-initialising a table */
128 var allSettings = DataTable.settings;
129 for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
130 {
131 var s = allSettings[i];
132
133 /* Base check on table node */
134 if (
135 s.nTable == this ||
136 (s.nTHead && s.nTHead.parentNode == this) ||
137 (s.nTFoot && s.nTFoot.parentNode == this)
138 ) {
139 var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
140 var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
141
142 if ( emptyInit || bRetrieve )
143 {
144 return s.oInstance;
145 }
146 else if ( bDestroy )
147 {
148 new DataTable.Api(s).destroy();
149 break;
150 }
151 else
152 {
153 _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
154 return;
155 }
156 }
157
158 /* If the element we are initialising has the same ID as a table which was previously
159 * initialised, but the table nodes don't match (from before) then we destroy the old
160 * instance by simply deleting it. This is under the assumption that the table has been
161 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
162 */
163 if ( s.sTableId == this.id )
164 {
165 allSettings.splice( i, 1 );
166 break;
167 }
168 }
169
170 /* Ensure the table has an ID - required for accessibility */
171 if ( sId === null || sId === "" )
172 {
173 sId = "DataTables_Table_"+(DataTable.ext._unique++);
174 this.id = sId;
175 }
176
177 /* Create the settings object for this table and set some of the default parameters */
178 var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
179 "sDestroyWidth": $this[0].style.width,
180 "sInstance": sId,
181 "sTableId": sId,
182 colgroup: $('<colgroup>').prependTo(this),
183 fastData: function (row, column, type) {
184 return _fnGetCellData(oSettings, row, column, type);
185 }
186 } );
187 oSettings.nTable = this;
188 oSettings.oInit = oInit;
189
190 allSettings.push( oSettings );
191
192 // Make a single API instance available for internal handling
193 oSettings.api = new _Api( oSettings );
194
195 // Need to add the instance after the instance after the settings object has been added
196 // to the settings array, so we can self reference the table instance if more than one
197 oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
198
199 // Backwards compatibility, before we apply all the defaults
200 _fnCompatOpts( oInit );
201
202 // If the length menu is given, but the init display length is not, use the length menu
203 if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
204 {
205 oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0])
206 ? oInit.aLengthMenu[0][0]
207 : $.isPlainObject( oInit.aLengthMenu[0] )
208 ? oInit.aLengthMenu[0].value
209 : oInit.aLengthMenu[0];
210 }
211
212 // Apply the defaults and init options to make a single init object will all
213 // options defined from defaults and instance options.
214 oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
215
216
217 // Map the initialisation options onto the settings object
218 _fnMap( oSettings.oFeatures, oInit, [
219 "bPaginate",
220 "bLengthChange",
221 "bFilter",
222 "bSort",
223 "bSortMulti",
224 "bInfo",
225 "bProcessing",
226 "bAutoWidth",
227 "bSortClasses",
228 "bServerSide",
229 "bDeferRender"
230 ] );
231 _fnMap( oSettings, oInit, [
232 "ajax",
233 "fnFormatNumber",
234 "sServerMethod",
235 "aaSorting",
236 "aaSortingFixed",
237 "aLengthMenu",
238 "sPaginationType",
239 "iStateDuration",
240 "bSortCellsTop",
241 "iTabIndex",
242 "sDom",
243 "fnStateLoadCallback",
244 "fnStateSaveCallback",
245 "renderer",
246 "searchDelay",
247 "rowId",
248 "caption",
249 "layout",
250 "orderDescReverse",
251 "typeDetect",
252 [ "iCookieDuration", "iStateDuration" ], // backwards compat
253 [ "oSearch", "oPreviousSearch" ],
254 [ "aoSearchCols", "aoPreSearchCols" ],
255 [ "iDisplayLength", "_iDisplayLength" ]
256 ] );
257 _fnMap( oSettings.oScroll, oInit, [
258 [ "sScrollX", "sX" ],
259 [ "sScrollXInner", "sXInner" ],
260 [ "sScrollY", "sY" ],
261 [ "bScrollCollapse", "bCollapse" ]
262 ] );
263 _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
264
265 /* Callback functions which are array driven */
266 _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback );
267 _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams );
268 _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams );
269 _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded );
270 _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback );
271 _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow );
272 _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback );
273 _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback );
274 _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete );
275 _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback );
276
277 oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
278
279 /* Browser support detection */
280 _fnBrowserDetect( oSettings );
281
282 var oClasses = oSettings.oClasses;
283
284 $.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
285 $this.addClass( oClasses.table );
286
287 if (! oSettings.oFeatures.bPaginate) {
288 oInit.iDisplayStart = 0;
289 }
290
291 if ( oSettings.iInitDisplayStart === undefined )
292 {
293 /* Display start point, taking into account the save saving */
294 oSettings.iInitDisplayStart = oInit.iDisplayStart;
295 oSettings._iDisplayStart = oInit.iDisplayStart;
296 }
297
298 var defer = oInit.iDeferLoading;
299 if ( defer !== null )
300 {
301 oSettings.deferLoading = true;
302
303 var tmp = Array.isArray(defer);
304 oSettings._iRecordsDisplay = tmp ? defer[0] : defer;
305 oSettings._iRecordsTotal = tmp ? defer[1] : defer;
306 }
307
308 /*
309 * Columns
310 * See if we should load columns automatically or use defined ones
311 */
312 var columnsInit = [];
313 var thead = this.getElementsByTagName('thead');
314 var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] );
315
316 // If we don't have a columns array, then generate one with nulls
317 if ( oInit.aoColumns ) {
318 columnsInit = oInit.aoColumns;
319 }
320 else if ( initHeaderLayout.length ) {
321 for ( i=0, iLen=initHeaderLayout[0].length ; i<iLen ; i++ ) {
322 columnsInit.push( null );
323 }
324 }
325
326 // Add the columns
327 for ( i=0, iLen=columnsInit.length ; i<iLen ; i++ ) {
328 _fnAddColumn( oSettings );
329 }
330
331 // Apply the column definitions
332 _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) {
333 _fnColumnOptions( oSettings, iCol, oDef );
334 } );
335
336 /* HTML5 attribute detection - build an mData object automatically if the
337 * attributes are found
338 */
339 var rowOne = $this.children('tbody').find('tr').eq(0);
340
341 if ( rowOne.length ) {
342 var a = function ( cell, name ) {
343 return cell.getAttribute( 'data-'+name ) !== null ? name : null;
344 };
345
346 $( rowOne[0] ).children('th, td').each( function (i, cell) {
347 var col = oSettings.aoColumns[i];
348
349 if (! col) {
350 _fnLog( oSettings, 0, 'Incorrect column count', 18 );
351 }
352
353 if ( col.mData === i ) {
354 var sort = a( cell, 'sort' ) || a( cell, 'order' );
355 var filter = a( cell, 'filter' ) || a( cell, 'search' );
356
357 if ( sort !== null || filter !== null ) {
358 col.mData = {
359 _: i+'.display',
360 sort: sort !== null ? i+'.@data-'+sort : undefined,
361 type: sort !== null ? i+'.@data-'+sort : undefined,
362 filter: filter !== null ? i+'.@data-'+filter : undefined
363 };
364 col._isArrayHost = true;
365
366 _fnColumnOptions( oSettings, i );
367 }
368 }
369 } );
370 }
371
372 // Must be done after everything which can be overridden by the state saving!
373 _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
374
375 var features = oSettings.oFeatures;
376 if ( oInit.bStateSave )
377 {
378 features.bStateSave = true;
379 }
380
381 // If aaSorting is not defined, then we use the first indicator in asSorting
382 // in case that has been altered, so the default sort reflects that option
383 if ( oInit.aaSorting === undefined ) {
384 var sorting = oSettings.aaSorting;
385 for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
386 sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
387 }
388 }
389
390 // Do a first pass on the sorting classes (allows any size changes to be taken into
391 // account, and also will apply sorting disabled classes if disabled
392 _fnSortingClasses( oSettings );
393
394 _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
395 if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
396 _fnSortingClasses( oSettings );
397 }
398 } );
399
400
401 /*
402 * Table HTML init
403 * Cache the header, body and footer as required, creating them if needed
404 */
405 var caption = $this.children('caption');
406
407 if ( oSettings.caption ) {
408 if ( caption.length === 0 ) {
409 caption = $('<caption/>').appendTo( $this );
410 }
411
412 caption.html( oSettings.caption );
413 }
414
415 // Store the caption side, so we can remove the element from the document
416 // when creating the element
417 if (caption.length) {
418 caption[0]._captionSide = caption.css('caption-side');
419 oSettings.captionNode = caption[0];
420 }
421
422 if ( thead.length === 0 ) {
423 thead = $('<thead/>').appendTo($this);
424 }
425 oSettings.nTHead = thead[0];
426
427 var tbody = $this.children('tbody');
428 if ( tbody.length === 0 ) {
429 tbody = $('<tbody/>').insertAfter(thead);
430 }
431 oSettings.nTBody = tbody[0];
432
433 var tfoot = $this.children('tfoot');
434 if ( tfoot.length === 0 ) {
435 // If we are a scrolling table, and no footer has been given, then we need to create
436 // a tfoot element for the caption element to be appended to
437 tfoot = $('<tfoot/>').appendTo($this);
438 }
439 oSettings.nTFoot = tfoot[0];
440
441 // Copy the data index array
442 oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
443
444 // Initialisation complete - table can be drawn
445 oSettings.bInitialised = true;
446
447 // Language definitions
448 var oLanguage = oSettings.oLanguage;
449 $.extend( true, oLanguage, oInit.oLanguage );
450
451 if ( oLanguage.sUrl ) {
452 // Get the language definitions from a file
453 $.ajax( {
454 dataType: 'json',
455 url: oLanguage.sUrl,
456 success: function ( json ) {
457 _fnCamelToHungarian( defaults.oLanguage, json );
458 $.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
459
460 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
461 _fnInitialise( oSettings );
462 },
463 error: function () {
464 // Error occurred loading language file
465 _fnLog( oSettings, 0, 'i18n file loading error', 21 );
466
467 // Continue on as best we can
468 _fnInitialise( oSettings );
469 }
470 } );
471 }
472 else {
473 _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
474 _fnInitialise( oSettings );
475 }
476 } );
477 _that = null;
478 return this;
479 };
480
481
482
483 /**
484 * DataTables extensions
485 *
486 * This namespace acts as a collection area for plug-ins that can be used to
487 * extend DataTables capabilities. Indeed many of the build in methods
488 * use this method to provide their own capabilities (sorting methods for
489 * example).
490 *
491 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
492 * reasons
493 *
494 * @namespace
495 */
496 DataTable.ext = _ext = {
497 /**
498 * Buttons. For use with the Buttons extension for DataTables. This is
499 * defined here so other extensions can define buttons regardless of load
500 * order. It is _not_ used by DataTables core.
501 *
502 * @type object
503 * @default {}
504 */
505 buttons: {},
506
507
508 /**
509 * Element class names
510 *
511 * @type object
512 * @default {}
513 */
514 classes: {},
515
516
517 /**
518 * DataTables build type (expanded by the download builder)
519 *
520 * @type string
521 */
522 builder: "dt/dt-2.2.2/b-3.2.2/r-3.0.4",
523
524
525 /**
526 * Error reporting.
527 *
528 * How should DataTables report an error. Can take the value 'alert',
529 * 'throw', 'none' or a function.
530 *
531 * @type string|function
532 * @default alert
533 */
534 errMode: "alert",
535
536
537 /**
538 * Legacy so v1 plug-ins don't throw js errors on load
539 */
540 feature: [],
541
542 /**
543 * Feature plug-ins.
544 *
545 * This is an object of callbacks which provide the features for DataTables
546 * to be initialised via the `layout` option.
547 */
548 features: {},
549
550
551 /**
552 * Row searching.
553 *
554 * This method of searching is complimentary to the default type based
555 * searching, and a lot more comprehensive as it allows you complete control
556 * over the searching logic. Each element in this array is a function
557 * (parameters described below) that is called for every row in the table,
558 * and your logic decides if it should be included in the searching data set
559 * or not.
560 *
561 * Searching functions have the following input parameters:
562 *
563 * 1. `{object}` DataTables settings object: see
564 * {@link DataTable.models.oSettings}
565 * 2. `{array|object}` Data for the row to be processed (same as the
566 * original format that was passed in as the data source, or an array
567 * from a DOM data source
568 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
569 * can be useful to retrieve the `TR` element if you need DOM interaction.
570 *
571 * And the following return is expected:
572 *
573 * * {boolean} Include the row in the searched result set (true) or not
574 * (false)
575 *
576 * Note that as with the main search ability in DataTables, technically this
577 * is "filtering", since it is subtractive. However, for consistency in
578 * naming we call it searching here.
579 *
580 * @type array
581 * @default []
582 *
583 * @example
584 * // The following example shows custom search being applied to the
585 * // fourth column (i.e. the data[3] index) based on two input values
586 * // from the end-user, matching the data in a certain range.
587 * $.fn.dataTable.ext.search.push(
588 * function( settings, data, dataIndex ) {
589 * var min = document.getElementById('min').value * 1;
590 * var max = document.getElementById('max').value * 1;
591 * var version = data[3] == "-" ? 0 : data[3]*1;
592 *
593 * if ( min == "" && max == "" ) {
594 * return true;
595 * }
596 * else if ( min == "" && version < max ) {
597 * return true;
598 * }
599 * else if ( min < version && "" == max ) {
600 * return true;
601 * }
602 * else if ( min < version && version < max ) {
603 * return true;
604 * }
605 * return false;
606 * }
607 * );
608 */
609 search: [],
610
611
612 /**
613 * Selector extensions
614 *
615 * The `selector` option can be used to extend the options available for the
616 * selector modifier options (`selector-modifier` object data type) that
617 * each of the three built in selector types offer (row, column and cell +
618 * their plural counterparts). For example the Select extension uses this
619 * mechanism to provide an option to select only rows, columns and cells
620 * that have been marked as selected by the end user (`{selected: true}`),
621 * which can be used in conjunction with the existing built in selector
622 * options.
623 *
624 * Each property is an array to which functions can be pushed. The functions
625 * take three attributes:
626 *
627 * * Settings object for the host table
628 * * Options object (`selector-modifier` object type)
629 * * Array of selected item indexes
630 *
631 * The return is an array of the resulting item indexes after the custom
632 * selector has been applied.
633 *
634 * @type object
635 */
636 selector: {
637 cell: [],
638 column: [],
639 row: []
640 },
641
642
643 /**
644 * Legacy configuration options. Enable and disable legacy options that
645 * are available in DataTables.
646 *
647 * @type object
648 */
649 legacy: {
650 /**
651 * Enable / disable DataTables 1.9 compatible server-side processing
652 * requests
653 *
654 * @type boolean
655 * @default null
656 */
657 ajax: null
658 },
659
660
661 /**
662 * Pagination plug-in methods.
663 *
664 * Each entry in this object is a function and defines which buttons should
665 * be shown by the pagination rendering method that is used for the table:
666 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
667 * buttons are displayed in the document, while the functions here tell it
668 * what buttons to display. This is done by returning an array of button
669 * descriptions (what each button will do).
670 *
671 * Pagination types (the four built in options and any additional plug-in
672 * options defined here) can be used through the `paginationType`
673 * initialisation parameter.
674 *
675 * The functions defined take two parameters:
676 *
677 * 1. `{int} page` The current page index
678 * 2. `{int} pages` The number of pages in the table
679 *
680 * Each function is expected to return an array where each element of the
681 * array can be one of:
682 *
683 * * `first` - Jump to first page when activated
684 * * `last` - Jump to last page when activated
685 * * `previous` - Show previous page when activated
686 * * `next` - Show next page when activated
687 * * `{int}` - Show page of the index given
688 * * `{array}` - A nested array containing the above elements to add a
689 * containing 'DIV' element (might be useful for styling).
690 *
691 * Note that DataTables v1.9- used this object slightly differently whereby
692 * an object with two functions would be defined for each plug-in. That
693 * ability is still supported by DataTables 1.10+ to provide backwards
694 * compatibility, but this option of use is now decremented and no longer
695 * documented in DataTables 1.10+.
696 *
697 * @type object
698 * @default {}
699 *
700 * @example
701 * // Show previous, next and current page buttons only
702 * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
703 * return [ 'previous', page, 'next' ];
704 * };
705 */
706 pager: {},
707
708
709 renderer: {
710 pageButton: {},
711 header: {}
712 },
713
714
715 /**
716 * Ordering plug-ins - custom data source
717 *
718 * The extension options for ordering of data available here is complimentary
719 * to the default type based ordering that DataTables typically uses. It
720 * allows much greater control over the the data that is being used to
721 * order a column, but is necessarily therefore more complex.
722 *
723 * This type of ordering is useful if you want to do ordering based on data
724 * live from the DOM (for example the contents of an 'input' element) rather
725 * than just the static string that DataTables knows of.
726 *
727 * The way these plug-ins work is that you create an array of the values you
728 * wish to be ordering for the column in question and then return that
729 * array. The data in the array much be in the index order of the rows in
730 * the table (not the currently ordering order!). Which order data gathering
731 * function is run here depends on the `dt-init columns.orderDataType`
732 * parameter that is used for the column (if any).
733 *
734 * The functions defined take two parameters:
735 *
736 * 1. `{object}` DataTables settings object: see
737 * {@link DataTable.models.oSettings}
738 * 2. `{int}` Target column index
739 *
740 * Each function is expected to return an array:
741 *
742 * * `{array}` Data for the column to be ordering upon
743 *
744 * @type array
745 *
746 * @example
747 * // Ordering using `input` node values
748 * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
749 * {
750 * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
751 * return $('input', td).val();
752 * } );
753 * }
754 */
755 order: {},
756
757
758 /**
759 * Type based plug-ins.
760 *
761 * Each column in DataTables has a type assigned to it, either by automatic
762 * detection or by direct assignment using the `type` option for the column.
763 * The type of a column will effect how it is ordering and search (plug-ins
764 * can also make use of the column type if required).
765 *
766 * @namespace
767 */
768 type: {
769 /**
770 * Automatic column class assignment
771 */
772 className: {},
773
774 /**
775 * Type detection functions.
776 *
777 * The functions defined in this object are used to automatically detect
778 * a column's type, making initialisation of DataTables super easy, even
779 * when complex data is in the table.
780 *
781 * The functions defined take two parameters:
782 *
783 * 1. `{*}` Data from the column cell to be analysed
784 * 2. `{settings}` DataTables settings object. This can be used to
785 * perform context specific type detection - for example detection
786 * based on language settings such as using a comma for a decimal
787 * place. Generally speaking the options from the settings will not
788 * be required
789 *
790 * Each function is expected to return:
791 *
792 * * `{string|null}` Data type detected, or null if unknown (and thus
793 * pass it on to the other type detection functions.
794 *
795 * @type array
796 *
797 * @example
798 * // Currency type detection plug-in:
799 * $.fn.dataTable.ext.type.detect.push(
800 * function ( data, settings ) {
801 * // Check the numeric part
802 * if ( ! data.substring(1).match(/[0-9]/) ) {
803 * return null;
804 * }
805 *
806 * // Check prefixed by currency
807 * if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
808 * return 'currency';
809 * }
810 * return null;
811 * }
812 * );
813 */
814 detect: [],
815
816 /**
817 * Automatic renderer assignment
818 */
819 render: {},
820
821
822 /**
823 * Type based search formatting.
824 *
825 * The type based searching functions can be used to pre-format the
826 * data to be search on. For example, it can be used to strip HTML
827 * tags or to de-format telephone numbers for numeric only searching.
828 *
829 * Note that is a search is not defined for a column of a given type,
830 * no search formatting will be performed.
831 *
832 * Pre-processing of searching data plug-ins - When you assign the sType
833 * for a column (or have it automatically detected for you by DataTables
834 * or a type detection plug-in), you will typically be using this for
835 * custom sorting, but it can also be used to provide custom searching
836 * by allowing you to pre-processing the data and returning the data in
837 * the format that should be searched upon. This is done by adding
838 * functions this object with a parameter name which matches the sType
839 * for that target column. This is the corollary of <i>afnSortData</i>
840 * for searching data.
841 *
842 * The functions defined take a single parameter:
843 *
844 * 1. `{*}` Data from the column cell to be prepared for searching
845 *
846 * Each function is expected to return:
847 *
848 * * `{string|null}` Formatted string that will be used for the searching.
849 *
850 * @type object
851 * @default {}
852 *
853 * @example
854 * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
855 * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
856 * }
857 */
858 search: {},
859
860
861 /**
862 * Type based ordering.
863 *
864 * The column type tells DataTables what ordering to apply to the table
865 * when a column is sorted upon. The order for each type that is defined,
866 * is defined by the functions available in this object.
867 *
868 * Each ordering option can be described by three properties added to
869 * this object:
870 *
871 * * `{type}-pre` - Pre-formatting function
872 * * `{type}-asc` - Ascending order function
873 * * `{type}-desc` - Descending order function
874 *
875 * All three can be used together, only `{type}-pre` or only
876 * `{type}-asc` and `{type}-desc` together. It is generally recommended
877 * that only `{type}-pre` is used, as this provides the optimal
878 * implementation in terms of speed, although the others are provided
879 * for compatibility with existing Javascript sort functions.
880 *
881 * `{type}-pre`: Functions defined take a single parameter:
882 *
883 * 1. `{*}` Data from the column cell to be prepared for ordering
884 *
885 * And return:
886 *
887 * * `{*}` Data to be sorted upon
888 *
889 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
890 * functions, taking two parameters:
891 *
892 * 1. `{*}` Data to compare to the second parameter
893 * 2. `{*}` Data to compare to the first parameter
894 *
895 * And returning:
896 *
897 * * `{*}` Ordering match: <0 if first parameter should be sorted lower
898 * than the second parameter, ===0 if the two parameters are equal and
899 * >0 if the first parameter should be sorted height than the second
900 * parameter.
901 *
902 * @type object
903 * @default {}
904 *
905 * @example
906 * // Numeric ordering of formatted numbers with a pre-formatter
907 * $.extend( $.fn.dataTable.ext.type.order, {
908 * "string-pre": function(x) {
909 * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
910 * return parseFloat( a );
911 * }
912 * } );
913 *
914 * @example
915 * // Case-sensitive string ordering, with no pre-formatting method
916 * $.extend( $.fn.dataTable.ext.order, {
917 * "string-case-asc": function(x,y) {
918 * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
919 * },
920 * "string-case-desc": function(x,y) {
921 * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
922 * }
923 * } );
924 */
925 order: {}
926 },
927
928 /**
929 * Unique DataTables instance counter
930 *
931 * @type int
932 * @private
933 */
934 _unique: 0,
935
936
937 //
938 // Depreciated
939 // The following properties are retained for backwards compatibility only.
940 // The should not be used in new projects and will be removed in a future
941 // version
942 //
943
944 /**
945 * Version check function.
946 * @type function
947 * @depreciated Since 1.10
948 */
949 fnVersionCheck: DataTable.fnVersionCheck,
950
951
952 /**
953 * Index for what 'this' index API functions should use
954 * @type int
955 * @deprecated Since v1.10
956 */
957 iApiIndex: 0,
958
959
960 /**
961 * Software version
962 * @type string
963 * @deprecated Since v1.10
964 */
965 sVersion: DataTable.version
966 };
967
968
969 //
970 // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
971 //
972 $.extend( _ext, {
973 afnFiltering: _ext.search,
974 aTypes: _ext.type.detect,
975 ofnSearch: _ext.type.search,
976 oSort: _ext.type.order,
977 afnSortData: _ext.order,
978 aoFeatures: _ext.feature,
979 oStdClasses: _ext.classes,
980 oPagination: _ext.pager
981 } );
982
983
984 $.extend( DataTable.ext.classes, {
985 container: 'dt-container',
986 empty: {
987 row: 'dt-empty'
988 },
989 info: {
990 container: 'dt-info'
991 },
992 layout: {
993 row: 'dt-layout-row',
994 cell: 'dt-layout-cell',
995 tableRow: 'dt-layout-table',
996 tableCell: '',
997 start: 'dt-layout-start',
998 end: 'dt-layout-end',
999 full: 'dt-layout-full'
1000 },
1001 length: {
1002 container: 'dt-length',
1003 select: 'dt-input'
1004 },
1005 order: {
1006 canAsc: 'dt-orderable-asc',
1007 canDesc: 'dt-orderable-desc',
1008 isAsc: 'dt-ordering-asc',
1009 isDesc: 'dt-ordering-desc',
1010 none: 'dt-orderable-none',
1011 position: 'sorting_'
1012 },
1013 processing: {
1014 container: 'dt-processing'
1015 },
1016 scrolling: {
1017 body: 'dt-scroll-body',
1018 container: 'dt-scroll',
1019 footer: {
1020 self: 'dt-scroll-foot',
1021 inner: 'dt-scroll-footInner'
1022 },
1023 header: {
1024 self: 'dt-scroll-head',
1025 inner: 'dt-scroll-headInner'
1026 }
1027 },
1028 search: {
1029 container: 'dt-search',
1030 input: 'dt-input'
1031 },
1032 table: 'dataTable',
1033 tbody: {
1034 cell: '',
1035 row: ''
1036 },
1037 thead: {
1038 cell: '',
1039 row: ''
1040 },
1041 tfoot: {
1042 cell: '',
1043 row: ''
1044 },
1045 paging: {
1046 active: 'current',
1047 button: 'dt-paging-button',
1048 container: 'dt-paging',
1049 disabled: 'disabled',
1050 nav: ''
1051 }
1052 } );
1053
1054
1055 /*
1056 * It is useful to have variables which are scoped locally so only the
1057 * DataTables functions can access them and they don't leak into global space.
1058 * At the same time these functions are often useful over multiple files in the
1059 * core and API, so we list, or at least document, all variables which are used
1060 * by DataTables as private variables here. This also ensures that there is no
1061 * clashing of variable names and that they can easily referenced for reuse.
1062 */
1063
1064
1065 // Defined else where
1066 // _selector_run
1067 // _selector_opts
1068 // _selector_row_indexes
1069
1070 var _ext; // DataTable.ext
1071 var _Api; // DataTable.Api
1072 var _api_register; // DataTable.Api.register
1073 var _api_registerPlural; // DataTable.Api.registerPlural
1074
1075 var _re_dic = {};
1076 var _re_new_lines = /[\r\n\u2028]/g;
1077 var _re_html = /<([^>]*>)/g;
1078 var _max_str_len = Math.pow(2, 28);
1079
1080 // This is not strict ISO8601 - Date.parse() is quite lax, although
1081 // implementations differ between browsers.
1082 var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/;
1083
1084 // Escape regular expression special characters
1085 var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
1086
1087 // https://en.wikipedia.org/wiki/Foreign_exchange_market
1088 // - \u20BD - Russian ruble.
1089 // - \u20a9 - South Korean Won
1090 // - \u20BA - Turkish Lira
1091 // - \u20B9 - Indian Rupee
1092 // - R - Brazil (R$) and South Africa
1093 // - fr - Swiss Franc
1094 // - kr - Swedish krona, Norwegian krone and Danish krone
1095 // - \u2009 is thin space and \u202F is narrow no-break space, both used in many
1096 // - Ƀ - Bitcoin
1097 // - Ξ - Ethereum
1098 // standards as thousands separators.
1099 var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi;
1100
1101
1102 var _empty = function ( d ) {
1103 return !d || d === true || d === '-' ? true : false;
1104 };
1105
1106
1107 var _intVal = function ( s ) {
1108 var integer = parseInt( s, 10 );
1109 return !isNaN(integer) && isFinite(s) ? integer : null;
1110 };
1111
1112 // Convert from a formatted number with characters other than `.` as the
1113 // decimal place, to a Javascript number
1114 var _numToDecimal = function ( num, decimalPoint ) {
1115 // Cache created regular expressions for speed as this function is called often
1116 if ( ! _re_dic[ decimalPoint ] ) {
1117 _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
1118 }
1119 return typeof num === 'string' && decimalPoint !== '.' ?
1120 num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
1121 num;
1122 };
1123
1124
1125 var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) {
1126 var type = typeof d;
1127 var strType = type === 'string';
1128
1129 if ( type === 'number' || type === 'bigint') {
1130 return true;
1131 }
1132
1133 // If empty return immediately so there must be a number if it is a
1134 // formatted string (this stops the string "k", or "kr", etc being detected
1135 // as a formatted number for currency
1136 if ( allowEmpty && _empty( d ) ) {
1137 return true;
1138 }
1139
1140 if ( decimalPoint && strType ) {
1141 d = _numToDecimal( d, decimalPoint );
1142 }
1143
1144 if ( formatted && strType ) {
1145 d = d.replace( _re_formatted_numeric, '' );
1146 }
1147
1148 return !isNaN( parseFloat(d) ) && isFinite( d );
1149 };
1150
1151
1152 // A string without HTML in it can be considered to be HTML still
1153 var _isHtml = function ( d ) {
1154 return _empty( d ) || typeof d === 'string';
1155 };
1156
1157 // Is a string a number surrounded by HTML?
1158 var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) {
1159 if ( allowEmpty && _empty( d ) ) {
1160 return true;
1161 }
1162
1163 // input and select strings mean that this isn't just a number
1164 if (typeof d === 'string' && d.match(/<(input|select)/i)) {
1165 return null;
1166 }
1167
1168 var html = _isHtml( d );
1169 return ! html ?
1170 null :
1171 _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ?
1172 true :
1173 null;
1174 };
1175
1176
1177 var _pluck = function ( a, prop, prop2 ) {
1178 var out = [];
1179 var i=0, ien=a.length;
1180
1181 // Could have the test in the loop for slightly smaller code, but speed
1182 // is essential here
1183 if ( prop2 !== undefined ) {
1184 for ( ; i<ien ; i++ ) {
1185 if ( a[i] && a[i][ prop ] ) {
1186 out.push( a[i][ prop ][ prop2 ] );
1187 }
1188 }
1189 }
1190 else {
1191 for ( ; i<ien ; i++ ) {
1192 if ( a[i] ) {
1193 out.push( a[i][ prop ] );
1194 }
1195 }
1196 }
1197
1198 return out;
1199 };
1200
1201
1202 // Basically the same as _pluck, but rather than looping over `a` we use `order`
1203 // as the indexes to pick from `a`
1204 var _pluck_order = function ( a, order, prop, prop2 )
1205 {
1206 var out = [];
1207 var i=0, ien=order.length;
1208
1209 // Could have the test in the loop for slightly smaller code, but speed
1210 // is essential here
1211 if ( prop2 !== undefined ) {
1212 for ( ; i<ien ; i++ ) {
1213 if ( a[ order[i] ] && a[ order[i] ][ prop ] ) {
1214 out.push( a[ order[i] ][ prop ][ prop2 ] );
1215 }
1216 }
1217 }
1218 else {
1219 for ( ; i<ien ; i++ ) {
1220 if ( a[ order[i] ] ) {
1221 out.push( a[ order[i] ][ prop ] );
1222 }
1223 }
1224 }
1225
1226 return out;
1227 };
1228
1229
1230 var _range = function ( len, start )
1231 {
1232 var out = [];
1233 var end;
1234
1235 if ( start === undefined ) {
1236 start = 0;
1237 end = len;
1238 }
1239 else {
1240 end = start;
1241 start = len;
1242 }
1243
1244 for ( var i=start ; i<end ; i++ ) {
1245 out.push( i );
1246 }
1247
1248 return out;
1249 };
1250
1251
1252 var _removeEmpty = function ( a )
1253 {
1254 var out = [];
1255
1256 for ( var i=0, ien=a.length ; i<ien ; i++ ) {
1257 if ( a[i] ) { // careful - will remove all falsy values!
1258 out.push( a[i] );
1259 }
1260 }
1261
1262 return out;
1263 };
1264
1265 // Replaceable function in api.util
1266 var _stripHtml = function (input) {
1267 if (! input || typeof input !== 'string') {
1268 return input;
1269 }
1270
1271 // Irrelevant check to workaround CodeQL's false positive on the regex
1272 if (input.length > _max_str_len) {
1273 throw new Error('Exceeded max str len');
1274 }
1275
1276 var previous;
1277
1278 input = input.replace(_re_html, ''); // Complete tags
1279
1280 // Safety for incomplete script tag - use do / while to ensure that
1281 // we get all instances
1282 do {
1283 previous = input;
1284 input = input.replace(/<script/i, '');
1285 } while (input !== previous);
1286
1287 return previous;
1288 };
1289
1290 // Replaceable function in api.util
1291 var _escapeHtml = function ( d ) {
1292 if (Array.isArray(d)) {
1293 d = d.join(',');
1294 }
1295
1296 return typeof d === 'string' ?
1297 d
1298 .replace(/&/g, '&amp;')
1299 .replace(/</g, '&lt;')
1300 .replace(/>/g, '&gt;')
1301 .replace(/"/g, '&quot;') :
1302 d;
1303 };
1304
1305 // Remove diacritics from a string by decomposing it and then removing
1306 // non-ascii characters
1307 var _normalize = function (str, both) {
1308 if (typeof str !== 'string') {
1309 return str;
1310 }
1311
1312 // It is faster to just run `normalize` than it is to check if
1313 // we need to with a regex! (Check as it isn't available in old
1314 // Safari)
1315 var res = str.normalize
1316 ? str.normalize("NFD")
1317 : str;
1318
1319 // Equally, here we check if a regex is needed or not
1320 return res.length !== str.length
1321 ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "")
1322 : res;
1323 }
1324
1325 /**
1326 * Determine if all values in the array are unique. This means we can short
1327 * cut the _unique method at the cost of a single loop. A sorted array is used
1328 * to easily check the values.
1329 *
1330 * @param {array} src Source array
1331 * @return {boolean} true if all unique, false otherwise
1332 * @ignore
1333 */
1334 var _areAllUnique = function ( src ) {
1335 if ( src.length < 2 ) {
1336 return true;
1337 }
1338
1339 var sorted = src.slice().sort();
1340 var last = sorted[0];
1341
1342 for ( var i=1, ien=sorted.length ; i<ien ; i++ ) {
1343 if ( sorted[i] === last ) {
1344 return false;
1345 }
1346
1347 last = sorted[i];
1348 }
1349
1350 return true;
1351 };
1352
1353
1354 /**
1355 * Find the unique elements in a source array.
1356 *
1357 * @param {array} src Source array
1358 * @return {array} Array of unique items
1359 * @ignore
1360 */
1361 var _unique = function ( src )
1362 {
1363 if (Array.from && Set) {
1364 return Array.from(new Set(src));
1365 }
1366
1367 if ( _areAllUnique( src ) ) {
1368 return src.slice();
1369 }
1370
1371 // A faster unique method is to use object keys to identify used values,
1372 // but this doesn't work with arrays or objects, which we must also
1373 // consider. See jsperf.app/compare-array-unique-versions/4 for more
1374 // information.
1375 var
1376 out = [],
1377 val,
1378 i, ien=src.length,
1379 j, k=0;
1380
1381 again: for ( i=0 ; i<ien ; i++ ) {
1382 val = src[i];
1383
1384 for ( j=0 ; j<k ; j++ ) {
1385 if ( out[j] === val ) {
1386 continue again;
1387 }
1388 }
1389
1390 out.push( val );
1391 k++;
1392 }
1393
1394 return out;
1395 };
1396
1397 // Surprisingly this is faster than [].concat.apply
1398 // https://jsperf.com/flatten-an-array-loop-vs-reduce/2
1399 var _flatten = function (out, val) {
1400 if (Array.isArray(val)) {
1401 for (var i=0 ; i<val.length ; i++) {
1402 _flatten(out, val[i]);
1403 }
1404 }
1405 else {
1406 out.push(val);
1407 }
1408
1409 return out;
1410 }
1411
1412 // Similar to jQuery's addClass, but use classList.add
1413 function _addClass(el, name) {
1414 if (name) {
1415 name.split(' ').forEach(function (n) {
1416 if (n) {
1417 // `add` does deduplication, so no need to check `contains`
1418 el.classList.add(n);
1419 }
1420 });
1421 }
1422 }
1423
1424 /**
1425 * DataTables utility methods
1426 *
1427 * This namespace provides helper methods that DataTables uses internally to
1428 * create a DataTable, but which are not exclusively used only for DataTables.
1429 * These methods can be used by extension authors to save the duplication of
1430 * code.
1431 *
1432 * @namespace
1433 */
1434 DataTable.util = {
1435 /**
1436 * Return a string with diacritic characters decomposed
1437 * @param {*} mixed Function or string to normalize
1438 * @param {*} both Return original string and the normalized string
1439 * @returns String or undefined
1440 */
1441 diacritics: function (mixed, both) {
1442 var type = typeof mixed;
1443
1444 if (type !== 'function') {
1445 return _normalize(mixed, both);
1446 }
1447 _normalize = mixed;
1448 },
1449
1450 /**
1451 * Debounce a function
1452 *
1453 * @param {function} fn Function to be called
1454 * @param {integer} freq Call frequency in mS
1455 * @return {function} Wrapped function
1456 */
1457 debounce: function ( fn, timeout ) {
1458 var timer;
1459
1460 return function () {
1461 var that = this;
1462 var args = arguments;
1463
1464 clearTimeout(timer);
1465
1466 timer = setTimeout( function () {
1467 fn.apply(that, args);
1468 }, timeout || 250 );
1469 };
1470 },
1471
1472 /**
1473 * Throttle the calls to a function. Arguments and context are maintained
1474 * for the throttled function.
1475 *
1476 * @param {function} fn Function to be called
1477 * @param {integer} freq Call frequency in mS
1478 * @return {function} Wrapped function
1479 */
1480 throttle: function ( fn, freq ) {
1481 var
1482 frequency = freq !== undefined ? freq : 200,
1483 last,
1484 timer;
1485
1486 return function () {
1487 var
1488 that = this,
1489 now = +new Date(),
1490 args = arguments;
1491
1492 if ( last && now < last + frequency ) {
1493 clearTimeout( timer );
1494
1495 timer = setTimeout( function () {
1496 last = undefined;
1497 fn.apply( that, args );
1498 }, frequency );
1499 }
1500 else {
1501 last = now;
1502 fn.apply( that, args );
1503 }
1504 };
1505 },
1506
1507 /**
1508 * Escape a string such that it can be used in a regular expression
1509 *
1510 * @param {string} val string to escape
1511 * @returns {string} escaped string
1512 */
1513 escapeRegex: function ( val ) {
1514 return val.replace( _re_escape_regex, '\\$1' );
1515 },
1516
1517 /**
1518 * Create a function that will write to a nested object or array
1519 * @param {*} source JSON notation string
1520 * @returns Write function
1521 */
1522 set: function ( source ) {
1523 if ( $.isPlainObject( source ) ) {
1524 /* Unlike get, only the underscore (global) option is used for for
1525 * setting data since we don't know the type here. This is why an object
1526 * option is not documented for `mData` (which is read/write), but it is
1527 * for `mRender` which is read only.
1528 */
1529 return DataTable.util.set( source._ );
1530 }
1531 else if ( source === null ) {
1532 // Nothing to do when the data source is null
1533 return function () {};
1534 }
1535 else if ( typeof source === 'function' ) {
1536 return function (data, val, meta) {
1537 source( data, 'set', val, meta );
1538 };
1539 }
1540 else if (
1541 typeof source === 'string' && (source.indexOf('.') !== -1 ||
1542 source.indexOf('[') !== -1 || source.indexOf('(') !== -1)
1543 ) {
1544 // Like the get, we need to get data from a nested object
1545 var setData = function (data, val, src) {
1546 var a = _fnSplitObjNotation( src ), b;
1547 var aLast = a[a.length-1];
1548 var arrayNotation, funcNotation, o, innerSrc;
1549
1550 for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) {
1551 // Protect against prototype pollution
1552 if (a[i] === '__proto__' || a[i] === 'constructor') {
1553 throw new Error('Cannot set prototype values');
1554 }
1555
1556 // Check if we are dealing with an array notation request
1557 arrayNotation = a[i].match(__reArray);
1558 funcNotation = a[i].match(__reFn);
1559
1560 if ( arrayNotation ) {
1561 a[i] = a[i].replace(__reArray, '');
1562 data[ a[i] ] = [];
1563
1564 // Get the remainder of the nested object to set so we can recurse
1565 b = a.slice();
1566 b.splice( 0, i+1 );
1567 innerSrc = b.join('.');
1568
1569 // Traverse each entry in the array setting the properties requested
1570 if ( Array.isArray( val ) ) {
1571 for ( var j=0, jLen=val.length ; j<jLen ; j++ ) {
1572 o = {};
1573 setData( o, val[j], innerSrc );
1574 data[ a[i] ].push( o );
1575 }
1576 }
1577 else {
1578 // We've been asked to save data to an array, but it
1579 // isn't array data to be saved. Best that can be done
1580 // is to just save the value.
1581 data[ a[i] ] = val;
1582 }
1583
1584 // The inner call to setData has already traversed through the remainder
1585 // of the source and has set the data, thus we can exit here
1586 return;
1587 }
1588 else if ( funcNotation ) {
1589 // Function call
1590 a[i] = a[i].replace(__reFn, '');
1591 data = data[ a[i] ]( val );
1592 }
1593
1594 // If the nested object doesn't currently exist - since we are
1595 // trying to set the value - create it
1596 if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) {
1597 data[ a[i] ] = {};
1598 }
1599 data = data[ a[i] ];
1600 }
1601
1602 // Last item in the input - i.e, the actual set
1603 if ( aLast.match(__reFn ) ) {
1604 // Function call
1605 data = data[ aLast.replace(__reFn, '') ]( val );
1606 }
1607 else {
1608 // If array notation is used, we just want to strip it and use the property name
1609 // and assign the value. If it isn't used, then we get the result we want anyway
1610 data[ aLast.replace(__reArray, '') ] = val;
1611 }
1612 };
1613
1614 return function (data, val) { // meta is also passed in, but not used
1615 return setData( data, val, source );
1616 };
1617 }
1618 else {
1619 // Array or flat object mapping
1620 return function (data, val) { // meta is also passed in, but not used
1621 data[source] = val;
1622 };
1623 }
1624 },
1625
1626 /**
1627 * Create a function that will read nested objects from arrays, based on JSON notation
1628 * @param {*} source JSON notation string
1629 * @returns Value read
1630 */
1631 get: function ( source ) {
1632 if ( $.isPlainObject( source ) ) {
1633 // Build an object of get functions, and wrap them in a single call
1634 var o = {};
1635 $.each( source, function (key, val) {
1636 if ( val ) {
1637 o[key] = DataTable.util.get( val );
1638 }
1639 } );
1640
1641 return function (data, type, row, meta) {
1642 var t = o[type] || o._;
1643 return t !== undefined ?
1644 t(data, type, row, meta) :
1645 data;
1646 };
1647 }
1648 else if ( source === null ) {
1649 // Give an empty string for rendering / sorting etc
1650 return function (data) { // type, row and meta also passed, but not used
1651 return data;
1652 };
1653 }
1654 else if ( typeof source === 'function' ) {
1655 return function (data, type, row, meta) {
1656 return source( data, type, row, meta );
1657 };
1658 }
1659 else if (
1660 typeof source === 'string' && (source.indexOf('.') !== -1 ||
1661 source.indexOf('[') !== -1 || source.indexOf('(') !== -1)
1662 ) {
1663 /* If there is a . in the source string then the data source is in a
1664 * nested object so we loop over the data for each level to get the next
1665 * level down. On each loop we test for undefined, and if found immediately
1666 * return. This allows entire objects to be missing and sDefaultContent to
1667 * be used if defined, rather than throwing an error
1668 */
1669 var fetchData = function (data, type, src) {
1670 var arrayNotation, funcNotation, out, innerSrc;
1671
1672 if ( src !== "" ) {
1673 var a = _fnSplitObjNotation( src );
1674
1675 for ( var i=0, iLen=a.length ; i<iLen ; i++ ) {
1676 // Check if we are dealing with special notation
1677 arrayNotation = a[i].match(__reArray);
1678 funcNotation = a[i].match(__reFn);
1679
1680 if ( arrayNotation ) {
1681 // Array notation
1682 a[i] = a[i].replace(__reArray, '');
1683
1684 // Condition allows simply [] to be passed in
1685 if ( a[i] !== "" ) {
1686 data = data[ a[i] ];
1687 }
1688 out = [];
1689
1690 // Get the remainder of the nested object to get
1691 a.splice( 0, i+1 );
1692 innerSrc = a.join('.');
1693
1694 // Traverse each entry in the array getting the properties requested
1695 if ( Array.isArray( data ) ) {
1696 for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
1697 out.push( fetchData( data[j], type, innerSrc ) );
1698 }
1699 }
1700
1701 // If a string is given in between the array notation indicators, that
1702 // is used to join the strings together, otherwise an array is returned
1703 var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
1704 data = (join==="") ? out : out.join(join);
1705
1706 // The inner call to fetchData has already traversed through the remainder
1707 // of the source requested, so we exit from the loop
1708 break;
1709 }
1710 else if ( funcNotation ) {
1711 // Function call
1712 a[i] = a[i].replace(__reFn, '');
1713 data = data[ a[i] ]();
1714 continue;
1715 }
1716
1717 if (data === null || data[ a[i] ] === null) {
1718 return null;
1719 }
1720 else if ( data === undefined || data[ a[i] ] === undefined ) {
1721 return undefined;
1722 }
1723
1724 data = data[ a[i] ];
1725 }
1726 }
1727
1728 return data;
1729 };
1730
1731 return function (data, type) { // row and meta also passed, but not used
1732 return fetchData( data, type, source );
1733 };
1734 }
1735 else {
1736 // Array or flat object mapping
1737 return function (data) { // row and meta also passed, but not used
1738 return data[source];
1739 };
1740 }
1741 },
1742
1743 stripHtml: function (mixed) {
1744 var type = typeof mixed;
1745
1746 if (type === 'function') {
1747 _stripHtml = mixed;
1748 return;
1749 }
1750 else if (type === 'string') {
1751 return _stripHtml(mixed);
1752 }
1753 return mixed;
1754 },
1755
1756 escapeHtml: function (mixed) {
1757 var type = typeof mixed;
1758
1759 if (type === 'function') {
1760 _escapeHtml = mixed;
1761 return;
1762 }
1763 else if (type === 'string' || Array.isArray(mixed)) {
1764 return _escapeHtml(mixed);
1765 }
1766 return mixed;
1767 },
1768
1769 unique: _unique
1770 };
1771
1772
1773
1774 /**
1775 * Create a mapping object that allows camel case parameters to be looked up
1776 * for their Hungarian counterparts. The mapping is stored in a private
1777 * parameter called `_hungarianMap` which can be accessed on the source object.
1778 * @param {object} o
1779 * @memberof DataTable#oApi
1780 */
1781 function _fnHungarianMap ( o )
1782 {
1783 var
1784 hungarian = 'a aa ai ao as b fn i m o s ',
1785 match,
1786 newKey,
1787 map = {};
1788
1789 $.each( o, function (key) {
1790 match = key.match(/^([^A-Z]+?)([A-Z])/);
1791
1792 if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
1793 {
1794 newKey = key.replace( match[0], match[2].toLowerCase() );
1795 map[ newKey ] = key;
1796
1797 if ( match[1] === 'o' )
1798 {
1799 _fnHungarianMap( o[key] );
1800 }
1801 }
1802 } );
1803
1804 o._hungarianMap = map;
1805 }
1806
1807
1808 /**
1809 * Convert from camel case parameters to Hungarian, based on a Hungarian map
1810 * created by _fnHungarianMap.
1811 * @param {object} src The model object which holds all parameters that can be
1812 * mapped.
1813 * @param {object} user The object to convert from camel case to Hungarian.
1814 * @param {boolean} force When set to `true`, properties which already have a
1815 * Hungarian value in the `user` object will be overwritten. Otherwise they
1816 * won't be.
1817 * @memberof DataTable#oApi
1818 */
1819 function _fnCamelToHungarian ( src, user, force )
1820 {
1821 if ( ! src._hungarianMap ) {
1822 _fnHungarianMap( src );
1823 }
1824
1825 var hungarianKey;
1826
1827 $.each( user, function (key) {
1828 hungarianKey = src._hungarianMap[ key ];
1829
1830 if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
1831 {
1832 // For objects, we need to buzz down into the object to copy parameters
1833 if ( hungarianKey.charAt(0) === 'o' )
1834 {
1835 // Copy the camelCase options over to the hungarian
1836 if ( ! user[ hungarianKey ] ) {
1837 user[ hungarianKey ] = {};
1838 }
1839 $.extend( true, user[hungarianKey], user[key] );
1840
1841 _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
1842 }
1843 else {
1844 user[hungarianKey] = user[ key ];
1845 }
1846 }
1847 } );
1848 }
1849
1850 /**
1851 * Map one parameter onto another
1852 * @param {object} o Object to map
1853 * @param {*} knew The new parameter name
1854 * @param {*} old The old parameter name
1855 */
1856 var _fnCompatMap = function ( o, knew, old ) {
1857 if ( o[ knew ] !== undefined ) {
1858 o[ old ] = o[ knew ];
1859 }
1860 };
1861
1862
1863 /**
1864 * Provide backwards compatibility for the main DT options. Note that the new
1865 * options are mapped onto the old parameters, so this is an external interface
1866 * change only.
1867 * @param {object} init Object to map
1868 */
1869 function _fnCompatOpts ( init )
1870 {
1871 _fnCompatMap( init, 'ordering', 'bSort' );
1872 _fnCompatMap( init, 'orderMulti', 'bSortMulti' );
1873 _fnCompatMap( init, 'orderClasses', 'bSortClasses' );
1874 _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
1875 _fnCompatMap( init, 'order', 'aaSorting' );
1876 _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' );
1877 _fnCompatMap( init, 'paging', 'bPaginate' );
1878 _fnCompatMap( init, 'pagingType', 'sPaginationType' );
1879 _fnCompatMap( init, 'pageLength', 'iDisplayLength' );
1880 _fnCompatMap( init, 'searching', 'bFilter' );
1881
1882 // Boolean initialisation of x-scrolling
1883 if ( typeof init.sScrollX === 'boolean' ) {
1884 init.sScrollX = init.sScrollX ? '100%' : '';
1885 }
1886 if ( typeof init.scrollX === 'boolean' ) {
1887 init.scrollX = init.scrollX ? '100%' : '';
1888 }
1889
1890 // Column search objects are in an array, so it needs to be converted
1891 // element by element
1892 var searchCols = init.aoSearchCols;
1893
1894 if ( searchCols ) {
1895 for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
1896 if ( searchCols[i] ) {
1897 _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
1898 }
1899 }
1900 }
1901
1902 // Enable search delay if server-side processing is enabled
1903 if (init.serverSide && ! init.searchDelay) {
1904 init.searchDelay = 400;
1905 }
1906 }
1907
1908
1909 /**
1910 * Provide backwards compatibility for column options. Note that the new options
1911 * are mapped onto the old parameters, so this is an external interface change
1912 * only.
1913 * @param {object} init Object to map
1914 */
1915 function _fnCompatCols ( init )
1916 {
1917 _fnCompatMap( init, 'orderable', 'bSortable' );
1918 _fnCompatMap( init, 'orderData', 'aDataSort' );
1919 _fnCompatMap( init, 'orderSequence', 'asSorting' );
1920 _fnCompatMap( init, 'orderDataType', 'sortDataType' );
1921
1922 // orderData can be given as an integer
1923 var dataSort = init.aDataSort;
1924 if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) {
1925 init.aDataSort = [ dataSort ];
1926 }
1927 }
1928
1929
1930 /**
1931 * Browser feature detection for capabilities, quirks
1932 * @param {object} settings dataTables settings object
1933 * @memberof DataTable#oApi
1934 */
1935 function _fnBrowserDetect( settings )
1936 {
1937 // We don't need to do this every time DataTables is constructed, the values
1938 // calculated are specific to the browser and OS configuration which we
1939 // don't expect to change between initialisations
1940 if ( ! DataTable.__browser ) {
1941 var browser = {};
1942 DataTable.__browser = browser;
1943
1944 // Scrolling feature / quirks detection
1945 var n = $('<div/>')
1946 .css( {
1947 position: 'fixed',
1948 top: 0,
1949 left: -1 * window.pageXOffset, // allow for scrolling
1950 height: 1,
1951 width: 1,
1952 overflow: 'hidden'
1953 } )
1954 .append(
1955 $('<div/>')
1956 .css( {
1957 position: 'absolute',
1958 top: 1,
1959 left: 1,
1960 width: 100,
1961 overflow: 'scroll'
1962 } )
1963 .append(
1964 $('<div/>')
1965 .css( {
1966 width: '100%',
1967 height: 10
1968 } )
1969 )
1970 )
1971 .appendTo( 'body' );
1972
1973 var outer = n.children();
1974 var inner = outer.children();
1975
1976 // Get scrollbar width
1977 browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
1978
1979 // In rtl text layout, some browsers (most, but not all) will place the
1980 // scrollbar on the left, rather than the right.
1981 browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
1982
1983 n.remove();
1984 }
1985
1986 $.extend( settings.oBrowser, DataTable.__browser );
1987 settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
1988 }
1989
1990 /**
1991 * Add a column to the list used for the table with default values
1992 * @param {object} oSettings dataTables settings object
1993 * @memberof DataTable#oApi
1994 */
1995 function _fnAddColumn( oSettings )
1996 {
1997 // Add column to aoColumns array
1998 var oDefaults = DataTable.defaults.column;
1999 var iCol = oSettings.aoColumns.length;
2000 var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
2001 "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
2002 "mData": oDefaults.mData ? oDefaults.mData : iCol,
2003 idx: iCol,
2004 searchFixed: {},
2005 colEl: $('<col>').attr('data-dt-column', iCol)
2006 } );
2007 oSettings.aoColumns.push( oCol );
2008
2009 // Add search object for column specific search. Note that the `searchCols[ iCol ]`
2010 // passed into extend can be undefined. This allows the user to give a default
2011 // with only some of the parameters defined, and also not give a default
2012 var searchCols = oSettings.aoPreSearchCols;
2013 searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
2014 }
2015
2016
2017 /**
2018 * Apply options for a column
2019 * @param {object} oSettings dataTables settings object
2020 * @param {int} iCol column index to consider
2021 * @param {object} oOptions object with sType, bVisible and bSearchable etc
2022 * @memberof DataTable#oApi
2023 */
2024 function _fnColumnOptions( oSettings, iCol, oOptions )
2025 {
2026 var oCol = oSettings.aoColumns[ iCol ];
2027
2028 /* User specified column options */
2029 if ( oOptions !== undefined && oOptions !== null )
2030 {
2031 // Backwards compatibility
2032 _fnCompatCols( oOptions );
2033
2034 // Map camel case parameters to their Hungarian counterparts
2035 _fnCamelToHungarian( DataTable.defaults.column, oOptions, true );
2036
2037 /* Backwards compatibility for mDataProp */
2038 if ( oOptions.mDataProp !== undefined && !oOptions.mData )
2039 {
2040 oOptions.mData = oOptions.mDataProp;
2041 }
2042
2043 if ( oOptions.sType )
2044 {
2045 oCol._sManualType = oOptions.sType;
2046 }
2047
2048 // `class` is a reserved word in Javascript, so we need to provide
2049 // the ability to use a valid name for the camel case input
2050 if ( oOptions.className && ! oOptions.sClass )
2051 {
2052 oOptions.sClass = oOptions.className;
2053 }
2054
2055 var origClass = oCol.sClass;
2056
2057 $.extend( oCol, oOptions );
2058 _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
2059
2060 // Merge class from previously defined classes with this one, rather than just
2061 // overwriting it in the extend above
2062 if (origClass !== oCol.sClass) {
2063 oCol.sClass = origClass + ' ' + oCol.sClass;
2064 }
2065
2066 /* iDataSort to be applied (backwards compatibility), but aDataSort will take
2067 * priority if defined
2068 */
2069 if ( oOptions.iDataSort !== undefined )
2070 {
2071 oCol.aDataSort = [ oOptions.iDataSort ];
2072 }
2073 _fnMap( oCol, oOptions, "aDataSort" );
2074 }
2075
2076 /* Cache the data get and set functions for speed */
2077 var mDataSrc = oCol.mData;
2078 var mData = _fnGetObjectDataFn( mDataSrc );
2079
2080 // The `render` option can be given as an array to access the helper rendering methods.
2081 // The first element is the rendering method to use, the rest are the parameters to pass
2082 if ( oCol.mRender && Array.isArray( oCol.mRender ) ) {
2083 var copy = oCol.mRender.slice();
2084 var name = copy.shift();
2085
2086 oCol.mRender = DataTable.render[name].apply(window, copy);
2087 }
2088
2089 oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
2090
2091 var attrTest = function( src ) {
2092 return typeof src === 'string' && src.indexOf('@') !== -1;
2093 };
2094 oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
2095 attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
2096 );
2097 oCol._setter = null;
2098
2099 oCol.fnGetData = function (rowData, type, meta) {
2100 var innerData = mData( rowData, type, undefined, meta );
2101
2102 return oCol._render && type ?
2103 oCol._render( innerData, type, rowData, meta ) :
2104 innerData;
2105 };
2106 oCol.fnSetData = function ( rowData, val, meta ) {
2107 return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
2108 };
2109
2110 // Indicate if DataTables should read DOM data as an object or array
2111 // Used in _fnGetRowElements
2112 if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) {
2113 oSettings._rowReadObject = true;
2114 }
2115
2116 /* Feature sorting overrides column specific when off */
2117 if ( !oSettings.oFeatures.bSort )
2118 {
2119 oCol.bSortable = false;
2120 }
2121 }
2122
2123
2124 /**
2125 * Adjust the table column widths for new data. Note: you would probably want to
2126 * do a redraw after calling this function!
2127 * @param {object} settings dataTables settings object
2128 * @memberof DataTable#oApi
2129 */
2130 function _fnAdjustColumnSizing ( settings )
2131 {
2132 _fnCalculateColumnWidths( settings );
2133 _fnColumnSizes( settings );
2134
2135 var scroll = settings.oScroll;
2136 if ( scroll.sY !== '' || scroll.sX !== '') {
2137 _fnScrollDraw( settings );
2138 }
2139
2140 _fnCallbackFire( settings, null, 'column-sizing', [settings] );
2141 }
2142
2143 /**
2144 * Apply column sizes
2145 *
2146 * @param {*} settings DataTables settings object
2147 */
2148 function _fnColumnSizes ( settings )
2149 {
2150 var cols = settings.aoColumns;
2151
2152 for (var i=0 ; i<cols.length ; i++) {
2153 var width = _fnColumnsSumWidth(settings, [i], false, false);
2154
2155 cols[i].colEl.css('width', width);
2156
2157 if (settings.oScroll.sX) {
2158 cols[i].colEl.css('min-width', width);
2159 }
2160 }
2161 }
2162
2163
2164 /**
2165 * Convert the index of a visible column to the index in the data array (take account
2166 * of hidden columns)
2167 * @param {object} oSettings dataTables settings object
2168 * @param {int} iMatch Visible column index to lookup
2169 * @returns {int} i the data index
2170 * @memberof DataTable#oApi
2171 */
2172 function _fnVisibleToColumnIndex( oSettings, iMatch )
2173 {
2174 var aiVis = _fnGetColumns( oSettings, 'bVisible' );
2175
2176 return typeof aiVis[iMatch] === 'number' ?
2177 aiVis[iMatch] :
2178 null;
2179 }
2180
2181
2182 /**
2183 * Convert the index of an index in the data array and convert it to the visible
2184 * column index (take account of hidden columns)
2185 * @param {int} iMatch Column index to lookup
2186 * @param {object} oSettings dataTables settings object
2187 * @returns {int} i the data index
2188 * @memberof DataTable#oApi
2189 */
2190 function _fnColumnIndexToVisible( oSettings, iMatch )
2191 {
2192 var aiVis = _fnGetColumns( oSettings, 'bVisible' );
2193 var iPos = aiVis.indexOf(iMatch);
2194
2195 return iPos !== -1 ? iPos : null;
2196 }
2197
2198
2199 /**
2200 * Get the number of visible columns
2201 * @param {object} oSettings dataTables settings object
2202 * @returns {int} i the number of visible columns
2203 * @memberof DataTable#oApi
2204 */
2205 function _fnVisbleColumns( settings )
2206 {
2207 var layout = settings.aoHeader;
2208 var columns = settings.aoColumns;
2209 var vis = 0;
2210
2211 if ( layout.length ) {
2212 for ( var i=0, ien=layout[0].length ; i<ien ; i++ ) {
2213 if ( columns[i].bVisible && $(layout[0][i].cell).css('display') !== 'none' ) {
2214 vis++;
2215 }
2216 }
2217 }
2218
2219 return vis;
2220 }
2221
2222
2223 /**
2224 * Get an array of column indexes that match a given property
2225 * @param {object} oSettings dataTables settings object
2226 * @param {string} sParam Parameter in aoColumns to look for - typically
2227 * bVisible or bSearchable
2228 * @returns {array} Array of indexes with matched properties
2229 * @memberof DataTable#oApi
2230 */
2231 function _fnGetColumns( oSettings, sParam )
2232 {
2233 var a = [];
2234
2235 oSettings.aoColumns.map( function(val, i) {
2236 if ( val[sParam] ) {
2237 a.push( i );
2238 }
2239 } );
2240
2241 return a;
2242 }
2243
2244 /**
2245 * Allow the result from a type detection function to be `true` while
2246 * translating that into a string. Old type detection functions will
2247 * return the type name if it passes. An obect store would be better,
2248 * but not backwards compatible.
2249 *
2250 * @param {*} typeDetect Object or function for type detection
2251 * @param {*} res Result from the type detection function
2252 * @returns Type name or false
2253 */
2254 function _typeResult (typeDetect, res) {
2255 return res === true
2256 ? typeDetect._name
2257 : res;
2258 }
2259
2260 /**
2261 * Calculate the 'type' of a column
2262 * @param {object} settings dataTables settings object
2263 * @memberof DataTable#oApi
2264 */
2265 function _fnColumnTypes ( settings )
2266 {
2267 var columns = settings.aoColumns;
2268 var data = settings.aoData;
2269 var types = DataTable.ext.type.detect;
2270 var i, ien, j, jen, k, ken;
2271 var col, detectedType, cache;
2272
2273 // For each column, spin over the data type detection functions, seeing if one matches
2274 for ( i=0, ien=columns.length ; i<ien ; i++ ) {
2275 col = columns[i];
2276 cache = [];
2277
2278 if ( ! col.sType && col._sManualType ) {
2279 col.sType = col._sManualType;
2280 }
2281 else if ( ! col.sType ) {
2282 // With SSP type detection can be unreliable and error prone, so we provide a way
2283 // to turn it off.
2284 if (! settings.typeDetect) {
2285 return;
2286 }
2287
2288 for ( j=0, jen=types.length ; j<jen ; j++ ) {
2289 var typeDetect = types[j];
2290
2291 // There can be either one, or three type detection functions
2292 var oneOf = typeDetect.oneOf;
2293 var allOf = typeDetect.allOf || typeDetect;
2294 var init = typeDetect.init;
2295 var one = false;
2296
2297 detectedType = null;
2298
2299 // Fast detect based on column assignment
2300 if (init) {
2301 detectedType = _typeResult(typeDetect, init(settings, col, i));
2302
2303 if (detectedType) {
2304 col.sType = detectedType;
2305 break;
2306 }
2307 }
2308
2309 for ( k=0, ken=data.length ; k<ken ; k++ ) {
2310 if (! data[k]) {
2311 continue;
2312 }
2313
2314 // Use a cache array so we only need to get the type data
2315 // from the formatter once (when using multiple detectors)
2316 if ( cache[k] === undefined ) {
2317 cache[k] = _fnGetCellData( settings, k, i, 'type' );
2318 }
2319
2320 // Only one data point in the column needs to match this function
2321 if (oneOf && ! one) {
2322 one = _typeResult(typeDetect, oneOf( cache[k], settings ));
2323 }
2324
2325 // All data points need to match this function
2326 detectedType = _typeResult(typeDetect, allOf( cache[k], settings ));
2327
2328 // If null, then this type can't apply to this column, so
2329 // rather than testing all cells, break out. There is an
2330 // exception for the last type which is `html`. We need to
2331 // scan all rows since it is possible to mix string and HTML
2332 // types
2333 if ( ! detectedType && j !== types.length-3 ) {
2334 break;
2335 }
2336
2337 // Only a single match is needed for html type since it is
2338 // bottom of the pile and very similar to string - but it
2339 // must not be empty
2340 if ( detectedType === 'html' && ! _empty(cache[k]) ) {
2341 break;
2342 }
2343 }
2344
2345 // Type is valid for all data points in the column - use this
2346 // type
2347 if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) {
2348 col.sType = detectedType;
2349 break;
2350 }
2351 }
2352
2353 // Fall back - if no type was detected, always use string
2354 if ( ! col.sType ) {
2355 col.sType = 'string';
2356 }
2357 }
2358
2359 // Set class names for header / footer for auto type classes
2360 var autoClass = _ext.type.className[col.sType];
2361
2362 if (autoClass) {
2363 _columnAutoClass(settings.aoHeader, i, autoClass);
2364 _columnAutoClass(settings.aoFooter, i, autoClass);
2365 }
2366
2367 var renderer = _ext.type.render[col.sType];
2368
2369 // This can only happen once! There is no way to remove
2370 // a renderer. After the first time the renderer has
2371 // already been set so createTr will run the renderer itself.
2372 if (renderer && ! col._render) {
2373 col._render = DataTable.util.get(renderer);
2374
2375 _columnAutoRender(settings, i);
2376 }
2377 }
2378 }
2379
2380 /**
2381 * Apply an auto detected renderer to data which doesn't yet have
2382 * a renderer
2383 */
2384 function _columnAutoRender(settings, colIdx) {
2385 var data = settings.aoData;
2386
2387 for (var i=0 ; i<data.length ; i++) {
2388 if (data[i].nTr) {
2389 // We have to update the display here since there is no
2390 // invalidation check for the data
2391 var display = _fnGetCellData( settings, i, colIdx, 'display' );
2392
2393 data[i].displayData[colIdx] = display;
2394 _fnWriteCell(data[i].anCells[colIdx], display);
2395
2396 // No need to update sort / filter data since it has
2397 // been invalidated and will be re-read with the
2398 // renderer now applied
2399 }
2400 }
2401 }
2402
2403 /**
2404 * Apply a class name to a column's header cells
2405 */
2406 function _columnAutoClass(container, colIdx, className) {
2407 container.forEach(function (row) {
2408 if (row[colIdx] && row[colIdx].unique) {
2409 _addClass(row[colIdx].cell, className);
2410 }
2411 });
2412 }
2413
2414 /**
2415 * Take the column definitions and static columns arrays and calculate how
2416 * they relate to column indexes. The callback function will then apply the
2417 * definition found for a column to a suitable configuration object.
2418 * @param {object} oSettings dataTables settings object
2419 * @param {array} aoColDefs The aoColumnDefs array that is to be applied
2420 * @param {array} aoCols The aoColumns array that defines columns individually
2421 * @param {array} headerLayout Layout for header as it was loaded
2422 * @param {function} fn Callback function - takes two parameters, the calculated
2423 * column index and the definition for that column.
2424 * @memberof DataTable#oApi
2425 */
2426 function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, headerLayout, fn )
2427 {
2428 var i, iLen, j, jLen, k, kLen, def;
2429 var columns = oSettings.aoColumns;
2430
2431 if ( aoCols ) {
2432 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
2433 if (aoCols[i] && aoCols[i].name) {
2434 columns[i].sName = aoCols[i].name;
2435 }
2436 }
2437 }
2438
2439 // Column definitions with aTargets
2440 if ( aoColDefs )
2441 {
2442 /* Loop over the definitions array - loop in reverse so first instance has priority */
2443 for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
2444 {
2445 def = aoColDefs[i];
2446
2447 /* Each definition can target multiple columns, as it is an array */
2448 var aTargets = def.target !== undefined
2449 ? def.target
2450 : def.targets !== undefined
2451 ? def.targets
2452 : def.aTargets;
2453
2454 if ( ! Array.isArray( aTargets ) )
2455 {
2456 aTargets = [ aTargets ];
2457 }
2458
2459 for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
2460 {
2461 var target = aTargets[j];
2462
2463 if ( typeof target === 'number' && target >= 0 )
2464 {
2465 /* Add columns that we don't yet know about */
2466 while( columns.length <= target )
2467 {
2468 _fnAddColumn( oSettings );
2469 }
2470
2471 /* Integer, basic index */
2472 fn( target, def );
2473 }
2474 else if ( typeof target === 'number' && target < 0 )
2475 {
2476 /* Negative integer, right to left column counting */
2477 fn( columns.length+target, def );
2478 }
2479 else if ( typeof target === 'string' )
2480 {
2481 for ( k=0, kLen=columns.length ; k<kLen ; k++ ) {
2482 if (target === '_all') {
2483 // Apply to all columns
2484 fn( k, def );
2485 }
2486 else if (target.indexOf(':name') !== -1) {
2487 // Column selector
2488 if (columns[k].sName === target.replace(':name', '')) {
2489 fn( k, def );
2490 }
2491 }
2492 else {
2493 // Cell selector
2494 headerLayout.forEach(function (row) {
2495 if (row[k]) {
2496 var cell = $(row[k].cell);
2497
2498 // Legacy support. Note that it means that we don't support
2499 // an element name selector only, since they are treated as
2500 // class names for 1.x compat.
2501 if (target.match(/^[a-z][\w-]*$/i)) {
2502 target = '.' + target;
2503 }
2504
2505 if (cell.is( target )) {
2506 fn( k, def );
2507 }
2508 }
2509 });
2510 }
2511 }
2512 }
2513 }
2514 }
2515 }
2516
2517 // Statically defined columns array
2518 if ( aoCols ) {
2519 for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
2520 fn( i, aoCols[i] );
2521 }
2522 }
2523 }
2524
2525
2526 /**
2527 * Get the width for a given set of columns
2528 *
2529 * @param {*} settings DataTables settings object
2530 * @param {*} targets Columns - comma separated string or array of numbers
2531 * @param {*} original Use the original width (true) or calculated (false)
2532 * @param {*} incVisible Include visible columns (true) or not (false)
2533 * @returns Combined CSS value
2534 */
2535 function _fnColumnsSumWidth( settings, targets, original, incVisible ) {
2536 if ( ! Array.isArray( targets ) ) {
2537 targets = _fnColumnsFromHeader( targets );
2538 }
2539
2540 var sum = 0;
2541 var unit;
2542 var columns = settings.aoColumns;
2543
2544 for ( var i=0, ien=targets.length ; i<ien ; i++ ) {
2545 var column = columns[ targets[i] ];
2546 var definedWidth = original ?
2547 column.sWidthOrig :
2548 column.sWidth;
2549
2550 if ( ! incVisible && column.bVisible === false ) {
2551 continue;
2552 }
2553
2554 if ( definedWidth === null || definedWidth === undefined ) {
2555 return null; // can't determine a defined width - browser defined
2556 }
2557 else if ( typeof definedWidth === 'number' ) {
2558 unit = 'px';
2559 sum += definedWidth;
2560 }
2561 else {
2562 var matched = definedWidth.match(/([\d\.]+)([^\d]*)/);
2563
2564 if ( matched ) {
2565 sum += matched[1] * 1;
2566 unit = matched.length === 3 ?
2567 matched[2] :
2568 'px';
2569 }
2570 }
2571 }
2572
2573 return sum + unit;
2574 }
2575
2576 function _fnColumnsFromHeader( cell )
2577 {
2578 var attr = $(cell).closest('[data-dt-column]').attr('data-dt-column');
2579
2580 if ( ! attr ) {
2581 return [];
2582 }
2583
2584 return attr.split(',').map( function (val) {
2585 return val * 1;
2586 } );
2587 }
2588 /**
2589 * Add a data array to the table, creating DOM node etc. This is the parallel to
2590 * _fnGatherData, but for adding rows from a Javascript source, rather than a
2591 * DOM source.
2592 * @param {object} settings dataTables settings object
2593 * @param {array} data data array to be added
2594 * @param {node} [tr] TR element to add to the table - optional. If not given,
2595 * DataTables will create a row automatically
2596 * @param {array} [tds] Array of TD|TH elements for the row - must be given
2597 * if nTr is.
2598 * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
2599 * @memberof DataTable#oApi
2600 */
2601 function _fnAddData ( settings, dataIn, tr, tds )
2602 {
2603 /* Create the object for storing information about this new row */
2604 var rowIdx = settings.aoData.length;
2605 var rowModel = $.extend( true, {}, DataTable.models.oRow, {
2606 src: tr ? 'dom' : 'data',
2607 idx: rowIdx
2608 } );
2609
2610 rowModel._aData = dataIn;
2611 settings.aoData.push( rowModel );
2612
2613 var columns = settings.aoColumns;
2614
2615 for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
2616 {
2617 // Invalidate the column types as the new data needs to be revalidated
2618 columns[i].sType = null;
2619 }
2620
2621 /* Add to the display array */
2622 settings.aiDisplayMaster.push( rowIdx );
2623
2624 var id = settings.rowIdFn( dataIn );
2625 if ( id !== undefined ) {
2626 settings.aIds[ id ] = rowModel;
2627 }
2628
2629 /* Create the DOM information, or register it if already present */
2630 if ( tr || ! settings.oFeatures.bDeferRender )
2631 {
2632 _fnCreateTr( settings, rowIdx, tr, tds );
2633 }
2634
2635 return rowIdx;
2636 }
2637
2638
2639 /**
2640 * Add one or more TR elements to the table. Generally we'd expect to
2641 * use this for reading data from a DOM sourced table, but it could be
2642 * used for an TR element. Note that if a TR is given, it is used (i.e.
2643 * it is not cloned).
2644 * @param {object} settings dataTables settings object
2645 * @param {array|node|jQuery} trs The TR element(s) to add to the table
2646 * @returns {array} Array of indexes for the added rows
2647 * @memberof DataTable#oApi
2648 */
2649 function _fnAddTr( settings, trs )
2650 {
2651 var row;
2652
2653 // Allow an individual node to be passed in
2654 if ( ! (trs instanceof $) ) {
2655 trs = $(trs);
2656 }
2657
2658 return trs.map( function (i, el) {
2659 row = _fnGetRowElements( settings, el );
2660 return _fnAddData( settings, row.data, el, row.cells );
2661 } );
2662 }
2663
2664
2665 /**
2666 * Get the data for a given cell from the internal cache, taking into account data mapping
2667 * @param {object} settings dataTables settings object
2668 * @param {int} rowIdx aoData row id
2669 * @param {int} colIdx Column index
2670 * @param {string} type data get type ('display', 'type' 'filter|search' 'sort|order')
2671 * @returns {*} Cell data
2672 * @memberof DataTable#oApi
2673 */
2674 function _fnGetCellData( settings, rowIdx, colIdx, type )
2675 {
2676 if (type === 'search') {
2677 type = 'filter';
2678 }
2679 else if (type === 'order') {
2680 type = 'sort';
2681 }
2682
2683 var row = settings.aoData[rowIdx];
2684
2685 if (! row) {
2686 return undefined;
2687 }
2688
2689 var draw = settings.iDraw;
2690 var col = settings.aoColumns[colIdx];
2691 var rowData = row._aData;
2692 var defaultContent = col.sDefaultContent;
2693 var cellData = col.fnGetData( rowData, type, {
2694 settings: settings,
2695 row: rowIdx,
2696 col: colIdx
2697 } );
2698
2699 // Allow for a node being returned for non-display types
2700 if (type !== 'display' && cellData && typeof cellData === 'object' && cellData.nodeName) {
2701 cellData = cellData.innerHTML;
2702 }
2703
2704 if ( cellData === undefined ) {
2705 if ( settings.iDrawError != draw && defaultContent === null ) {
2706 _fnLog( settings, 0, "Requested unknown parameter "+
2707 (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
2708 " for row "+rowIdx+", column "+colIdx, 4 );
2709 settings.iDrawError = draw;
2710 }
2711 return defaultContent;
2712 }
2713
2714 // When the data source is null and a specific data type is requested (i.e.
2715 // not the original data), we can use default column data
2716 if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) {
2717 cellData = defaultContent;
2718 }
2719 else if ( typeof cellData === 'function' ) {
2720 // If the data source is a function, then we run it and use the return,
2721 // executing in the scope of the data object (for instances)
2722 return cellData.call( rowData );
2723 }
2724
2725 if ( cellData === null && type === 'display' ) {
2726 return '';
2727 }
2728
2729 if ( type === 'filter' ) {
2730 var fomatters = DataTable.ext.type.search;
2731
2732 if ( fomatters[ col.sType ] ) {
2733 cellData = fomatters[ col.sType ]( cellData );
2734 }
2735 }
2736
2737 return cellData;
2738 }
2739
2740
2741 /**
2742 * Set the value for a specific cell, into the internal data cache
2743 * @param {object} settings dataTables settings object
2744 * @param {int} rowIdx aoData row id
2745 * @param {int} colIdx Column index
2746 * @param {*} val Value to set
2747 * @memberof DataTable#oApi
2748 */
2749 function _fnSetCellData( settings, rowIdx, colIdx, val )
2750 {
2751 var col = settings.aoColumns[colIdx];
2752 var rowData = settings.aoData[rowIdx]._aData;
2753
2754 col.fnSetData( rowData, val, {
2755 settings: settings,
2756 row: rowIdx,
2757 col: colIdx
2758 } );
2759 }
2760
2761 /**
2762 * Write a value to a cell
2763 * @param {*} td Cell
2764 * @param {*} val Value
2765 */
2766 function _fnWriteCell(td, val)
2767 {
2768 if (val && typeof val === 'object' && val.nodeName) {
2769 $(td)
2770 .empty()
2771 .append(val);
2772 }
2773 else {
2774 td.innerHTML = val;
2775 }
2776 }
2777
2778
2779 // Private variable that is used to match action syntax in the data property object
2780 var __reArray = /\[.*?\]$/;
2781 var __reFn = /\(\)$/;
2782
2783 /**
2784 * Split string on periods, taking into account escaped periods
2785 * @param {string} str String to split
2786 * @return {array} Split string
2787 */
2788 function _fnSplitObjNotation( str )
2789 {
2790 var parts = str.match(/(\\.|[^.])+/g) || [''];
2791
2792 return parts.map( function ( s ) {
2793 return s.replace(/\\\./g, '.');
2794 } );
2795 }
2796
2797
2798 /**
2799 * Return a function that can be used to get data from a source object, taking
2800 * into account the ability to use nested objects as a source
2801 * @param {string|int|function} mSource The data source for the object
2802 * @returns {function} Data get function
2803 * @memberof DataTable#oApi
2804 */
2805 var _fnGetObjectDataFn = DataTable.util.get;
2806
2807
2808 /**
2809 * Return a function that can be used to set data from a source object, taking
2810 * into account the ability to use nested objects as a source
2811 * @param {string|int|function} mSource The data source for the object
2812 * @returns {function} Data set function
2813 * @memberof DataTable#oApi
2814 */
2815 var _fnSetObjectDataFn = DataTable.util.set;
2816
2817
2818 /**
2819 * Return an array with the full table data
2820 * @param {object} oSettings dataTables settings object
2821 * @returns array {array} aData Master data array
2822 * @memberof DataTable#oApi
2823 */
2824 function _fnGetDataMaster ( settings )
2825 {
2826 return _pluck( settings.aoData, '_aData' );
2827 }
2828
2829
2830 /**
2831 * Nuke the table
2832 * @param {object} oSettings dataTables settings object
2833 * @memberof DataTable#oApi
2834 */
2835 function _fnClearTable( settings )
2836 {
2837 settings.aoData.length = 0;
2838 settings.aiDisplayMaster.length = 0;
2839 settings.aiDisplay.length = 0;
2840 settings.aIds = {};
2841 }
2842
2843
2844 /**
2845 * Mark cached data as invalid such that a re-read of the data will occur when
2846 * the cached data is next requested. Also update from the data source object.
2847 *
2848 * @param {object} settings DataTables settings object
2849 * @param {int} rowIdx Row index to invalidate
2850 * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom'
2851 * or 'data'
2852 * @param {int} [colIdx] Column index to invalidate. If undefined the whole
2853 * row will be invalidated
2854 * @memberof DataTable#oApi
2855 *
2856 * @todo For the modularisation of v1.11 this will need to become a callback, so
2857 * the sort and filter methods can subscribe to it. That will required
2858 * initialisation options for sorting, which is why it is not already baked in
2859 */
2860 function _fnInvalidate( settings, rowIdx, src, colIdx )
2861 {
2862 var row = settings.aoData[ rowIdx ];
2863 var i, ien;
2864
2865 // Remove the cached data for the row
2866 row._aSortData = null;
2867 row._aFilterData = null;
2868 row.displayData = null;
2869
2870 // Are we reading last data from DOM or the data object?
2871 if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
2872 // Read the data from the DOM
2873 row._aData = _fnGetRowElements(
2874 settings, row, colIdx, colIdx === undefined ? undefined : row._aData
2875 )
2876 .data;
2877 }
2878 else {
2879 // Reading from data object, update the DOM
2880 var cells = row.anCells;
2881 var display = _fnGetRowDisplay(settings, rowIdx);
2882
2883 if ( cells ) {
2884 if ( colIdx !== undefined ) {
2885 _fnWriteCell(cells[colIdx], display[colIdx]);
2886 }
2887 else {
2888 for ( i=0, ien=cells.length ; i<ien ; i++ ) {
2889 _fnWriteCell(cells[i], display[i]);
2890 }
2891 }
2892 }
2893 }
2894
2895 // Column specific invalidation
2896 var cols = settings.aoColumns;
2897 if ( colIdx !== undefined ) {
2898 // Type - the data might have changed
2899 cols[ colIdx ].sType = null;
2900
2901 // Max length string. Its a fairly cheep recalculation, so not worth
2902 // something more complicated
2903 cols[ colIdx ].maxLenString = null;
2904 }
2905 else {
2906 for ( i=0, ien=cols.length ; i<ien ; i++ ) {
2907 cols[i].sType = null;
2908 cols[i].maxLenString = null;
2909 }
2910
2911 // Update DataTables special `DT_*` attributes for the row
2912 _fnRowAttributes( settings, row );
2913 }
2914 }
2915
2916
2917 /**
2918 * Build a data source object from an HTML row, reading the contents of the
2919 * cells that are in the row.
2920 *
2921 * @param {object} settings DataTables settings object
2922 * @param {node|object} TR element from which to read data or existing row
2923 * object from which to re-read the data from the cells
2924 * @param {int} [colIdx] Optional column index
2925 * @param {array|object} [d] Data source object. If `colIdx` is given then this
2926 * parameter should also be given and will be used to write the data into.
2927 * Only the column in question will be written
2928 * @returns {object} Object with two parameters: `data` the data read, in
2929 * document order, and `cells` and array of nodes (they can be useful to the
2930 * caller, so rather than needing a second traversal to get them, just return
2931 * them from here).
2932 * @memberof DataTable#oApi
2933 */
2934 function _fnGetRowElements( settings, row, colIdx, d )
2935 {
2936 var
2937 tds = [],
2938 td = row.firstChild,
2939 name, col, i=0, contents,
2940 columns = settings.aoColumns,
2941 objectRead = settings._rowReadObject;
2942
2943 // Allow the data object to be passed in, or construct
2944 d = d !== undefined ?
2945 d :
2946 objectRead ?
2947 {} :
2948 [];
2949
2950 var attr = function ( str, td ) {
2951 if ( typeof str === 'string' ) {
2952 var idx = str.indexOf('@');
2953
2954 if ( idx !== -1 ) {
2955 var attr = str.substring( idx+1 );
2956 var setter = _fnSetObjectDataFn( str );
2957 setter( d, td.getAttribute( attr ) );
2958 }
2959 }
2960 };
2961
2962 // Read data from a cell and store into the data object
2963 var cellProcess = function ( cell ) {
2964 if ( colIdx === undefined || colIdx === i ) {
2965 col = columns[i];
2966 contents = (cell.innerHTML).trim();
2967
2968 if ( col && col._bAttrSrc ) {
2969 var setter = _fnSetObjectDataFn( col.mData._ );
2970 setter( d, contents );
2971
2972 attr( col.mData.sort, cell );
2973 attr( col.mData.type, cell );
2974 attr( col.mData.filter, cell );
2975 }
2976 else {
2977 // Depending on the `data` option for the columns the data can
2978 // be read to either an object or an array.
2979 if ( objectRead ) {
2980 if ( ! col._setter ) {
2981 // Cache the setter function
2982 col._setter = _fnSetObjectDataFn( col.mData );
2983 }
2984 col._setter( d, contents );
2985 }
2986 else {
2987 d[i] = contents;
2988 }
2989 }
2990 }
2991
2992 i++;
2993 };
2994
2995 if ( td ) {
2996 // `tr` element was passed in
2997 while ( td ) {
2998 name = td.nodeName.toUpperCase();
2999
3000 if ( name == "TD" || name == "TH" ) {
3001 cellProcess( td );
3002 tds.push( td );
3003 }
3004
3005 td = td.nextSibling;
3006 }
3007 }
3008 else {
3009 // Existing row object passed in
3010 tds = row.anCells;
3011
3012 for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
3013 cellProcess( tds[j] );
3014 }
3015 }
3016
3017 // Read the ID from the DOM if present
3018 var rowNode = row.firstChild ? row : row.nTr;
3019
3020 if ( rowNode ) {
3021 var id = rowNode.getAttribute( 'id' );
3022
3023 if ( id ) {
3024 _fnSetObjectDataFn( settings.rowId )( d, id );
3025 }
3026 }
3027
3028 return {
3029 data: d,
3030 cells: tds
3031 };
3032 }
3033
3034 /**
3035 * Render and cache a row's display data for the columns, if required
3036 * @returns
3037 */
3038 function _fnGetRowDisplay (settings, rowIdx) {
3039 var rowModal = settings.aoData[rowIdx];
3040 var columns = settings.aoColumns;
3041
3042 if (! rowModal.displayData) {
3043 // Need to render and cache
3044 rowModal.displayData = [];
3045
3046 for ( var colIdx=0, len=columns.length ; colIdx<len ; colIdx++ ) {
3047 rowModal.displayData.push(
3048 _fnGetCellData( settings, rowIdx, colIdx, 'display' )
3049 );
3050 }
3051 }
3052
3053 return rowModal.displayData;
3054 }
3055
3056 /**
3057 * Create a new TR element (and it's TD children) for a row
3058 * @param {object} oSettings dataTables settings object
3059 * @param {int} iRow Row to consider
3060 * @param {node} [nTrIn] TR element to add to the table - optional. If not given,
3061 * DataTables will create a row automatically
3062 * @param {array} [anTds] Array of TD|TH elements for the row - must be given
3063 * if nTr is.
3064 * @memberof DataTable#oApi
3065 */
3066 function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
3067 {
3068 var
3069 row = oSettings.aoData[iRow],
3070 rowData = row._aData,
3071 cells = [],
3072 nTr, nTd, oCol,
3073 i, iLen, create,
3074 trClass = oSettings.oClasses.tbody.row;
3075
3076 if ( row.nTr === null )
3077 {
3078 nTr = nTrIn || document.createElement('tr');
3079
3080 row.nTr = nTr;
3081 row.anCells = cells;
3082
3083 _addClass(nTr, trClass);
3084
3085 /* Use a private property on the node to allow reserve mapping from the node
3086 * to the aoData array for fast look up
3087 */
3088 nTr._DT_RowIndex = iRow;
3089
3090 /* Special parameters can be given by the data source to be used on the row */
3091 _fnRowAttributes( oSettings, row );
3092
3093 /* Process each column */
3094 for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3095 {
3096 oCol = oSettings.aoColumns[i];
3097 create = nTrIn && anTds[i] ? false : true;
3098
3099 nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
3100
3101 if (! nTd) {
3102 _fnLog( oSettings, 0, 'Incorrect column count', 18 );
3103 }
3104
3105 nTd._DT_CellIndex = {
3106 row: iRow,
3107 column: i
3108 };
3109
3110 cells.push( nTd );
3111
3112 var display = _fnGetRowDisplay(oSettings, iRow);
3113
3114 // Need to create the HTML if new, or if a rendering function is defined
3115 if (
3116 create ||
3117 (
3118 (oCol.mRender || oCol.mData !== i) &&
3119 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
3120 )
3121 ) {
3122 _fnWriteCell(nTd, display[i]);
3123 }
3124
3125 // column class
3126 _addClass(nTd, oCol.sClass);
3127
3128 // Visibility - add or remove as required
3129 if ( oCol.bVisible && create )
3130 {
3131 nTr.appendChild( nTd );
3132 }
3133 else if ( ! oCol.bVisible && ! create )
3134 {
3135 nTd.parentNode.removeChild( nTd );
3136 }
3137
3138 if ( oCol.fnCreatedCell )
3139 {
3140 oCol.fnCreatedCell.call( oSettings.oInstance,
3141 nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
3142 );
3143 }
3144 }
3145
3146 _fnCallbackFire( oSettings, 'aoRowCreatedCallback', 'row-created', [nTr, rowData, iRow, cells] );
3147 }
3148 else {
3149 _addClass(row.nTr, trClass);
3150 }
3151 }
3152
3153
3154 /**
3155 * Add attributes to a row based on the special `DT_*` parameters in a data
3156 * source object.
3157 * @param {object} settings DataTables settings object
3158 * @param {object} DataTables row object for the row to be modified
3159 * @memberof DataTable#oApi
3160 */
3161 function _fnRowAttributes( settings, row )
3162 {
3163 var tr = row.nTr;
3164 var data = row._aData;
3165
3166 if ( tr ) {
3167 var id = settings.rowIdFn( data );
3168
3169 if ( id ) {
3170 tr.id = id;
3171 }
3172
3173 if ( data.DT_RowClass ) {
3174 // Remove any classes added by DT_RowClass before
3175 var a = data.DT_RowClass.split(' ');
3176 row.__rowc = row.__rowc ?
3177 _unique( row.__rowc.concat( a ) ) :
3178 a;
3179
3180 $(tr)
3181 .removeClass( row.__rowc.join(' ') )
3182 .addClass( data.DT_RowClass );
3183 }
3184
3185 if ( data.DT_RowAttr ) {
3186 $(tr).attr( data.DT_RowAttr );
3187 }
3188
3189 if ( data.DT_RowData ) {
3190 $(tr).data( data.DT_RowData );
3191 }
3192 }
3193 }
3194
3195
3196 /**
3197 * Create the HTML header for the table
3198 * @param {object} oSettings dataTables settings object
3199 * @memberof DataTable#oApi
3200 */
3201 function _fnBuildHead( settings, side )
3202 {
3203 var classes = settings.oClasses;
3204 var columns = settings.aoColumns;
3205 var i, ien, row;
3206 var target = side === 'header'
3207 ? settings.nTHead
3208 : settings.nTFoot;
3209 var titleProp = side === 'header' ? 'sTitle' : side;
3210
3211 // Footer might be defined
3212 if (! target) {
3213 return;
3214 }
3215
3216 // If no cells yet and we have content for them, then create
3217 if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) {
3218 row = $('tr', target);
3219
3220 // Add a row if needed
3221 if (! row.length) {
3222 row = $('<tr/>').appendTo(target)
3223 }
3224
3225 // Add the number of cells needed to make up to the number of columns
3226 if (row.length === 1) {
3227 var cellCount = 0;
3228
3229 $('td, th', row).each(function () {
3230 cellCount += this.colSpan;
3231 });
3232
3233 for ( i=cellCount, ien=columns.length ; i<ien ; i++ ) {
3234 $('<th/>')
3235 .html( columns[i][titleProp] || '' )
3236 .appendTo( row );
3237 }
3238 }
3239 }
3240
3241 var detected = _fnDetectHeader( settings, target, true );
3242
3243 if (side === 'header') {
3244 settings.aoHeader = detected;
3245 $('tr', target).addClass(classes.thead.row);
3246 }
3247 else {
3248 settings.aoFooter = detected;
3249 $('tr', target).addClass(classes.tfoot.row);
3250 }
3251
3252 // Every cell needs to be passed through the renderer
3253 $(target).children('tr').children('th, td')
3254 .each( function () {
3255 _fnRenderer( settings, side )(
3256 settings, $(this), classes
3257 );
3258 } );
3259 }
3260
3261 /**
3262 * Build a layout structure for a header or footer
3263 *
3264 * @param {*} settings DataTables settings
3265 * @param {*} source Source layout array
3266 * @param {*} incColumns What columns should be included
3267 * @returns Layout array
3268 */
3269 function _fnHeaderLayout( settings, source, incColumns )
3270 {
3271 var row, column, cell;
3272 var local = [];
3273 var structure = [];
3274 var columns = settings.aoColumns;
3275 var columnCount = columns.length;
3276 var rowspan, colspan;
3277
3278 if ( ! source ) {
3279 return;
3280 }
3281
3282 // Default is to work on only visible columns
3283 if ( ! incColumns ) {
3284 incColumns = _range(columnCount)
3285 .filter(function (idx) {
3286 return columns[idx].bVisible;
3287 });
3288 }
3289
3290 // Make a copy of the master layout array, but with only the columns we want
3291 for ( row=0 ; row<source.length ; row++ ) {
3292 // Remove any columns we haven't selected
3293 local[row] = source[row].slice().filter(function (cell, i) {
3294 return incColumns.includes(i);
3295 });
3296
3297 // Prep the structure array - it needs an element for each row
3298 structure.push( [] );
3299 }
3300
3301 for ( row=0 ; row<local.length ; row++ ) {
3302 for ( column=0 ; column<local[row].length ; column++ ) {
3303 rowspan = 1;
3304 colspan = 1;
3305
3306 // Check to see if there is already a cell (row/colspan) covering our target
3307 // insert point. If there is, then there is nothing to do.
3308 if ( structure[row][column] === undefined ) {
3309 cell = local[row][column].cell;
3310
3311 // Expand for rowspan
3312 while (
3313 local[row+rowspan] !== undefined &&
3314 local[row][column].cell == local[row+rowspan][column].cell
3315 ) {
3316 structure[row+rowspan][column] = null;
3317 rowspan++;
3318 }
3319
3320 // And for colspan
3321 while (
3322 local[row][column+colspan] !== undefined &&
3323 local[row][column].cell == local[row][column+colspan].cell
3324 ) {
3325 // Which also needs to go over rows
3326 for ( var k=0 ; k<rowspan ; k++ ) {
3327 structure[row+k][column+colspan] = null;
3328 }
3329
3330 colspan++;
3331 }
3332
3333 var titleSpan = $('span.dt-column-title', cell);
3334
3335 structure[row][column] = {
3336 cell: cell,
3337 colspan: colspan,
3338 rowspan: rowspan,
3339 title: titleSpan.length
3340 ? titleSpan.html()
3341 : $(cell).html()
3342 };
3343 }
3344 }
3345 }
3346
3347 return structure;
3348 }
3349
3350
3351 /**
3352 * Draw the header (or footer) element based on the column visibility states.
3353 *
3354 * @param object oSettings dataTables settings object
3355 * @param array aoSource Layout array from _fnDetectHeader
3356 * @memberof DataTable#oApi
3357 */
3358 function _fnDrawHead( settings, source )
3359 {
3360 var layout = _fnHeaderLayout(settings, source);
3361 var tr, n;
3362
3363 for ( var row=0 ; row<source.length ; row++ ) {
3364 tr = source[row].row;
3365
3366 // All cells are going to be replaced, so empty out the row
3367 // Can't use $().empty() as that kills event handlers
3368 if (tr) {
3369 while( (n = tr.firstChild) ) {
3370 tr.removeChild( n );
3371 }
3372 }
3373
3374 for ( var column=0 ; column<layout[row].length ; column++ ) {
3375 var point = layout[row][column];
3376
3377 if (point) {
3378 $(point.cell)
3379 .appendTo(tr)
3380 .attr('rowspan', point.rowspan)
3381 .attr('colspan', point.colspan);
3382 }
3383 }
3384 }
3385 }
3386
3387
3388 /**
3389 * Insert the required TR nodes into the table for display
3390 * @param {object} oSettings dataTables settings object
3391 * @param ajaxComplete true after ajax call to complete rendering
3392 * @memberof DataTable#oApi
3393 */
3394 function _fnDraw( oSettings, ajaxComplete )
3395 {
3396 // Allow for state saving and a custom start position
3397 _fnStart( oSettings );
3398
3399 /* Provide a pre-callback function which can be used to cancel the draw is false is returned */
3400 var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
3401 if ( aPreDraw.indexOf(false) !== -1 )
3402 {
3403 _fnProcessingDisplay( oSettings, false );
3404 return;
3405 }
3406
3407 var anRows = [];
3408 var iRowCount = 0;
3409 var bServerSide = _fnDataSource( oSettings ) == 'ssp';
3410 var aiDisplay = oSettings.aiDisplay;
3411 var iDisplayStart = oSettings._iDisplayStart;
3412 var iDisplayEnd = oSettings.fnDisplayEnd();
3413 var columns = oSettings.aoColumns;
3414 var body = $(oSettings.nTBody);
3415
3416 oSettings.bDrawing = true;
3417
3418 /* Server-side processing draw intercept */
3419 if ( oSettings.deferLoading )
3420 {
3421 oSettings.deferLoading = false;
3422 oSettings.iDraw++;
3423 _fnProcessingDisplay( oSettings, false );
3424 }
3425 else if ( !bServerSide )
3426 {
3427 oSettings.iDraw++;
3428 }
3429 else if ( !oSettings.bDestroying && !ajaxComplete)
3430 {
3431 // Show loading message for server-side processing
3432 if (oSettings.iDraw === 0) {
3433 body.empty().append(_emptyRow(oSettings));
3434 }
3435
3436 _fnAjaxUpdate( oSettings );
3437 return;
3438 }
3439
3440 if ( aiDisplay.length !== 0 )
3441 {
3442 var iStart = bServerSide ? 0 : iDisplayStart;
3443 var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
3444
3445 for ( var j=iStart ; j<iEnd ; j++ )
3446 {
3447 var iDataIndex = aiDisplay[j];
3448 var aoData = oSettings.aoData[ iDataIndex ];
3449 if ( aoData.nTr === null )
3450 {
3451 _fnCreateTr( oSettings, iDataIndex );
3452 }
3453
3454 var nRow = aoData.nTr;
3455
3456 // Add various classes as needed
3457 for (var i=0 ; i<columns.length ; i++) {
3458 var col = columns[i];
3459 var td = aoData.anCells[i];
3460
3461 _addClass(td, _ext.type.className[col.sType]); // auto class
3462 _addClass(td, oSettings.oClasses.tbody.cell); // all cells
3463 }
3464
3465 // Row callback functions - might want to manipulate the row
3466 // iRowCount and j are not currently documented. Are they at all
3467 // useful?
3468 _fnCallbackFire( oSettings, 'aoRowCallback', null,
3469 [nRow, aoData._aData, iRowCount, j, iDataIndex] );
3470
3471 anRows.push( nRow );
3472 iRowCount++;
3473 }
3474 }
3475 else
3476 {
3477 anRows[ 0 ] = _emptyRow(oSettings);
3478 }
3479
3480 /* Header and footer callbacks */
3481 _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
3482 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
3483
3484 _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
3485 _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
3486
3487 // replaceChildren is faster, but only became widespread in 2020,
3488 // so a fall back in jQuery is provided for older browsers.
3489 if (body[0].replaceChildren) {
3490 body[0].replaceChildren.apply(body[0], anRows);
3491 }
3492 else {
3493 body.children().detach();
3494 body.append( $(anRows) );
3495 }
3496
3497 // Empty table needs a specific class
3498 $(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0);
3499
3500 /* Call all required callback functions for the end of a draw */
3501 _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings], true );
3502
3503 /* Draw is complete, sorting and filtering must be as well */
3504 oSettings.bSorted = false;
3505 oSettings.bFiltered = false;
3506 oSettings.bDrawing = false;
3507 }
3508
3509
3510 /**
3511 * Redraw the table - taking account of the various features which are enabled
3512 * @param {object} oSettings dataTables settings object
3513 * @param {boolean} [holdPosition] Keep the current paging position. By default
3514 * the paging is reset to the first page
3515 * @memberof DataTable#oApi
3516 */
3517 function _fnReDraw( settings, holdPosition, recompute )
3518 {
3519 var
3520 features = settings.oFeatures,
3521 sort = features.bSort,
3522 filter = features.bFilter;
3523
3524 if (recompute === undefined || recompute === true) {
3525 // Resolve any column types that are unknown due to addition or invalidation
3526 _fnColumnTypes( settings );
3527
3528 if ( sort ) {
3529 _fnSort( settings );
3530 }
3531
3532 if ( filter ) {
3533 _fnFilterComplete( settings, settings.oPreviousSearch );
3534 }
3535 else {
3536 // No filtering, so we want to just use the display master
3537 settings.aiDisplay = settings.aiDisplayMaster.slice();
3538 }
3539 }
3540
3541 if ( holdPosition !== true ) {
3542 settings._iDisplayStart = 0;
3543 }
3544
3545 // Let any modules know about the draw hold position state (used by
3546 // scrolling internally)
3547 settings._drawHold = holdPosition;
3548
3549 _fnDraw( settings );
3550
3551 settings._drawHold = false;
3552 }
3553
3554
3555 /*
3556 * Table is empty - create a row with an empty message in it
3557 */
3558 function _emptyRow ( settings ) {
3559 var oLang = settings.oLanguage;
3560 var zero = oLang.sZeroRecords;
3561 var dataSrc = _fnDataSource( settings );
3562
3563 if (
3564 (settings.iDraw < 1 && dataSrc === 'ssp') ||
3565 (settings.iDraw <= 1 && dataSrc === 'ajax')
3566 ) {
3567 zero = oLang.sLoadingRecords;
3568 }
3569 else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 )
3570 {
3571 zero = oLang.sEmptyTable;
3572 }
3573
3574 return $( '<tr/>' )
3575 .append( $('<td />', {
3576 'colSpan': _fnVisbleColumns( settings ),
3577 'class': settings.oClasses.empty.row
3578 } ).html( zero ) )[0];
3579 }
3580
3581
3582 /**
3583 * Expand the layout items into an object for the rendering function
3584 */
3585 function _layoutItems (row, align, items) {
3586 if ( Array.isArray(items)) {
3587 for (var i=0 ; i<items.length ; i++) {
3588 _layoutItems(row, align, items[i]);
3589 }
3590
3591 return;
3592 }
3593
3594 var rowCell = row[align];
3595
3596 // If it is an object, then there can be multiple features contained in it
3597 if ( $.isPlainObject( items ) ) {
3598 // A feature plugin cannot be named "features" due to this check
3599 if (items.features) {
3600 if (items.rowId) {
3601 row.id = items.rowId;
3602 }
3603 if (items.rowClass) {
3604 row.className = items.rowClass;
3605 }
3606
3607 rowCell.id = items.id;
3608 rowCell.className = items.className;
3609
3610 _layoutItems(row, align, items.features);
3611 }
3612 else {
3613 Object.keys(items).map(function (key) {
3614 rowCell.contents.push( {
3615 feature: key,
3616 opts: items[key]
3617 });
3618 });
3619 }
3620 }
3621 else {
3622 rowCell.contents.push(items);
3623 }
3624 }
3625
3626 /**
3627 * Find, or create a layout row
3628 */
3629 function _layoutGetRow(rows, rowNum, align) {
3630 var row;
3631
3632 // Find existing rows
3633 for (var i=0; i<rows.length; i++) {
3634 row = rows[i];
3635
3636 if (row.rowNum === rowNum) {
3637 // full is on its own, but start and end share a row
3638 if (
3639 (align === 'full' && row.full) ||
3640 ((align === 'start' || align === 'end') && (row.start || row.end))
3641 ) {
3642 if (! row[align]) {
3643 row[align] = {
3644 contents: []
3645 };
3646 }
3647
3648 return row;
3649 }
3650 }
3651 }
3652
3653 // If we get this far, then there was no match, create a new row
3654 row = {
3655 rowNum: rowNum
3656 };
3657
3658 row[align] = {
3659 contents: []
3660 };
3661
3662 rows.push(row);
3663
3664 return row;
3665 }
3666
3667 /**
3668 * Convert a `layout` object given by a user to the object structure needed
3669 * for the renderer. This is done twice, once for above and once for below
3670 * the table. Ordering must also be considered.
3671 *
3672 * @param {*} settings DataTables settings object
3673 * @param {*} layout Layout object to convert
3674 * @param {string} side `top` or `bottom`
3675 * @returns Converted array structure - one item for each row.
3676 */
3677 function _layoutArray ( settings, layout, side ) {
3678 var rows = [];
3679
3680 // Split out into an array
3681 $.each( layout, function ( pos, items ) {
3682 if (items === null) {
3683 return;
3684 }
3685
3686 var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/);
3687 var rowNum = parts[2]
3688 ? parts[2] * 1
3689 : 0;
3690 var align = parts[3]
3691 ? parts[3].toLowerCase()
3692 : 'full';
3693
3694 // Filter out the side we aren't interested in
3695 if (parts[1] !== side) {
3696 return;
3697 }
3698
3699 // Get or create the row we should attach to
3700 var row = _layoutGetRow(rows, rowNum, align);
3701
3702 _layoutItems(row, align, items);
3703 });
3704
3705 // Order by item identifier
3706 rows.sort( function ( a, b ) {
3707 var order1 = a.rowNum;
3708 var order2 = b.rowNum;
3709
3710 // If both in the same row, then the row with `full` comes first
3711 if (order1 === order2) {
3712 var ret = a.full && ! b.full ? -1 : 1;
3713
3714 return side === 'bottom'
3715 ? ret * -1
3716 : ret;
3717 }
3718
3719 return order2 - order1;
3720 } );
3721
3722 // Invert for below the table
3723 if ( side === 'bottom' ) {
3724 rows.reverse();
3725 }
3726
3727 for (var row = 0; row<rows.length; row++) {
3728 delete rows[row].rowNum;
3729
3730 _layoutResolve(settings, rows[row]);
3731 }
3732
3733 return rows;
3734 }
3735
3736
3737 /**
3738 * Convert the contents of a row's layout object to nodes that can be inserted
3739 * into the document by a renderer. Execute functions, look up plug-ins, etc.
3740 *
3741 * @param {*} settings DataTables settings object
3742 * @param {*} row Layout object for this row
3743 */
3744 function _layoutResolve( settings, row ) {
3745 var getFeature = function (feature, opts) {
3746 if ( ! _ext.features[ feature ] ) {
3747 _fnLog( settings, 0, 'Unknown feature: '+ feature );
3748 }
3749
3750 return _ext.features[ feature ].apply( this, [settings, opts] );
3751 };
3752
3753 var resolve = function ( item ) {
3754 if (! row[ item ]) {
3755 return;
3756 }
3757
3758 var line = row[ item ].contents;
3759
3760 for ( var i=0, ien=line.length ; i<ien ; i++ ) {
3761 if ( ! line[i] ) {
3762 continue;
3763 }
3764 else if ( typeof line[i] === 'string' ) {
3765 line[i] = getFeature( line[i], null );
3766 }
3767 else if ( $.isPlainObject(line[i]) ) {
3768 // If it's an object, it just has feature and opts properties from
3769 // the transform in _layoutArray
3770 line[i] = getFeature(line[i].feature, line[i].opts);
3771 }
3772 else if ( typeof line[i].node === 'function' ) {
3773 line[i] = line[i].node( settings );
3774 }
3775 else if ( typeof line[i] === 'function' ) {
3776 var inst = line[i]( settings );
3777
3778 line[i] = typeof inst.node === 'function' ?
3779 inst.node() :
3780 inst;
3781 }
3782 }
3783 };
3784
3785 resolve('start');
3786 resolve('end');
3787 resolve('full');
3788 }
3789
3790
3791 /**
3792 * Add the options to the page HTML for the table
3793 * @param {object} settings DataTables settings object
3794 * @memberof DataTable#oApi
3795 */
3796 function _fnAddOptionsHtml ( settings )
3797 {
3798 var classes = settings.oClasses;
3799 var table = $(settings.nTable);
3800
3801 // Wrapper div around everything DataTables controls
3802 var insert = $('<div/>')
3803 .attr({
3804 id: settings.sTableId+'_wrapper',
3805 'class': classes.container
3806 })
3807 .insertBefore(table);
3808
3809 settings.nTableWrapper = insert[0];
3810
3811 if (settings.sDom) {
3812 // Legacy
3813 _fnLayoutDom(settings, settings.sDom, insert);
3814 }
3815 else {
3816 var top = _layoutArray( settings, settings.layout, 'top' );
3817 var bottom = _layoutArray( settings, settings.layout, 'bottom' );
3818 var renderer = _fnRenderer( settings, 'layout' );
3819
3820 // Everything above - the renderer will actually insert the contents into the document
3821 top.forEach(function (item) {
3822 renderer( settings, insert, item );
3823 });
3824
3825 // The table - always the center of attention
3826 renderer( settings, insert, {
3827 full: {
3828 table: true,
3829 contents: [ _fnFeatureHtmlTable(settings) ]
3830 }
3831 } );
3832
3833 // Everything below
3834 bottom.forEach(function (item) {
3835 renderer( settings, insert, item );
3836 });
3837 }
3838
3839 // Processing floats on top, so it isn't an inserted feature
3840 _processingHtml( settings );
3841 }
3842
3843 /**
3844 * Draw the table with the legacy DOM property
3845 * @param {*} settings DT settings object
3846 * @param {*} dom DOM string
3847 * @param {*} insert Insert point
3848 */
3849 function _fnLayoutDom( settings, dom, insert )
3850 {
3851 var parts = dom.match(/(".*?")|('.*?')|./g);
3852 var featureNode, option, newNode, next, attr;
3853
3854 for ( var i=0 ; i<parts.length ; i++ ) {
3855 featureNode = null;
3856 option = parts[i];
3857
3858 if ( option == '<' ) {
3859 // New container div
3860 newNode = $('<div/>');
3861
3862 // Check to see if we should append an id and/or a class name to the container
3863 next = parts[i+1];
3864
3865 if ( next[0] == "'" || next[0] == '"' ) {
3866 attr = next.replace(/['"]/g, '');
3867
3868 var id = '', className;
3869
3870 /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
3871 * breaks the string into parts and applies them as needed
3872 */
3873 if ( attr.indexOf('.') != -1 ) {
3874 var split = attr.split('.');
3875
3876 id = split[0];
3877 className = split[1];
3878 }
3879 else if ( attr[0] == "#" ) {
3880 id = attr;
3881 }
3882 else {
3883 className = attr;
3884 }
3885
3886 newNode
3887 .attr('id', id.substring(1))
3888 .addClass(className);
3889
3890 i++; // Move along the position array
3891 }
3892
3893 insert.append( newNode );
3894 insert = newNode;
3895 }
3896 else if ( option == '>' ) {
3897 // End container div
3898 insert = insert.parent();
3899 }
3900 else if ( option == 't' ) {
3901 // Table
3902 featureNode = _fnFeatureHtmlTable( settings );
3903 }
3904 else
3905 {
3906 DataTable.ext.feature.forEach(function(feature) {
3907 if ( option == feature.cFeature ) {
3908 featureNode = feature.fnInit( settings );
3909 }
3910 });
3911 }
3912
3913 // Add to the display
3914 if ( featureNode ) {
3915 insert.append( featureNode );
3916 }
3917 }
3918 }
3919
3920
3921 /**
3922 * Use the DOM source to create up an array of header cells. The idea here is to
3923 * create a layout grid (array) of rows x columns, which contains a reference
3924 * to the cell that that point in the grid (regardless of col/rowspan), such that
3925 * any column / row could be removed and the new grid constructed
3926 * @param {node} thead The header/footer element for the table
3927 * @returns {array} Calculated layout array
3928 * @memberof DataTable#oApi
3929 */
3930 function _fnDetectHeader ( settings, thead, write )
3931 {
3932 var columns = settings.aoColumns;
3933 var rows = $(thead).children('tr');
3934 var row, cell;
3935 var i, k, l, iLen, shifted, column, colspan, rowspan;
3936 var isHeader = thead && thead.nodeName.toLowerCase() === 'thead';
3937 var layout = [];
3938 var unique;
3939 var shift = function ( a, i, j ) {
3940 var k = a[i];
3941 while ( k[j] ) {
3942 j++;
3943 }
3944 return j;
3945 };
3946
3947 // We know how many rows there are in the layout - so prep it
3948 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
3949 layout.push( [] );
3950 }
3951
3952 for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
3953 row = rows[i];
3954 column = 0;
3955
3956 // For every cell in the row..
3957 cell = row.firstChild;
3958 while ( cell ) {
3959 if (
3960 cell.nodeName.toUpperCase() == 'TD' ||
3961 cell.nodeName.toUpperCase() == 'TH'
3962 ) {
3963 var cols = [];
3964
3965 // Get the col and rowspan attributes from the DOM and sanitise them
3966 colspan = cell.getAttribute('colspan') * 1;
3967 rowspan = cell.getAttribute('rowspan') * 1;
3968 colspan = (!colspan || colspan===0 || colspan===1) ? 1 : colspan;
3969 rowspan = (!rowspan || rowspan===0 || rowspan===1) ? 1 : rowspan;
3970
3971 // There might be colspan cells already in this row, so shift our target
3972 // accordingly
3973 shifted = shift( layout, i, column );
3974
3975 // Cache calculation for unique columns
3976 unique = colspan === 1 ?
3977 true :
3978 false;
3979
3980 // Perform header setup
3981 if ( write ) {
3982 if (unique) {
3983 // Allow column options to be set from HTML attributes
3984 _fnColumnOptions( settings, shifted, $(cell).data() );
3985
3986 // Get the width for the column. This can be defined from the
3987 // width attribute, style attribute or `columns.width` option
3988 var columnDef = columns[shifted];
3989 var width = cell.getAttribute('width') || null;
3990 var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/);
3991 if ( t ) {
3992 width = t[1];
3993 }
3994
3995 columnDef.sWidthOrig = columnDef.sWidth || width;
3996
3997 if (isHeader) {
3998 // Column title handling - can be user set, or read from the DOM
3999 // This happens before the render, so the original is still in place
4000 if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) {
4001 cell.innerHTML = columnDef.sTitle;
4002 }
4003
4004 if (! columnDef.sTitle && unique) {
4005 columnDef.sTitle = _stripHtml(cell.innerHTML);
4006 columnDef.autoTitle = true;
4007 }
4008 }
4009 else {
4010 // Footer specific operations
4011 if (columnDef.footer) {
4012 cell.innerHTML = columnDef.footer;
4013 }
4014 }
4015
4016 // Fall back to the aria-label attribute on the table header if no ariaTitle is
4017 // provided.
4018 if (! columnDef.ariaTitle) {
4019 columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle;
4020 }
4021
4022 // Column specific class names
4023 if ( columnDef.className ) {
4024 $(cell).addClass( columnDef.className );
4025 }
4026 }
4027
4028 // Wrap the column title so we can write to it in future
4029 if ( $('span.dt-column-title', cell).length === 0) {
4030 $('<span>')
4031 .addClass('dt-column-title')
4032 .append(cell.childNodes)
4033 .appendTo(cell);
4034 }
4035
4036 if ( isHeader && $('span.dt-column-order', cell).length === 0) {
4037 $('<span>')
4038 .addClass('dt-column-order')
4039 .appendTo(cell);
4040 }
4041 }
4042
4043 // If there is col / rowspan, copy the information into the layout grid
4044 for ( l=0 ; l<colspan ; l++ ) {
4045 for ( k=0 ; k<rowspan ; k++ ) {
4046 layout[i+k][shifted+l] = {
4047 cell: cell,
4048 unique: unique
4049 };
4050
4051 layout[i+k].row = row;
4052 }
4053
4054 cols.push( shifted+l );
4055 }
4056
4057 // Assign an attribute so spanning cells can still be identified
4058 // as belonging to a column
4059 cell.setAttribute('data-dt-column', _unique(cols).join(','));
4060 }
4061
4062 cell = cell.nextSibling;
4063 }
4064 }
4065
4066 return layout;
4067 }
4068
4069 /**
4070 * Set the start position for draw
4071 * @param {object} oSettings dataTables settings object
4072 */
4073 function _fnStart( oSettings )
4074 {
4075 var bServerSide = _fnDataSource( oSettings ) == 'ssp';
4076 var iInitDisplayStart = oSettings.iInitDisplayStart;
4077
4078 // Check and see if we have an initial draw position from state saving
4079 if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
4080 {
4081 oSettings._iDisplayStart = bServerSide ?
4082 iInitDisplayStart :
4083 iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
4084 0 :
4085 iInitDisplayStart;
4086
4087 oSettings.iInitDisplayStart = -1;
4088 }
4089 }
4090
4091 /**
4092 * Create an Ajax call based on the table's settings, taking into account that
4093 * parameters can have multiple forms, and backwards compatibility.
4094 *
4095 * @param {object} oSettings dataTables settings object
4096 * @param {array} data Data to send to the server, required by
4097 * DataTables - may be augmented by developer callbacks
4098 * @param {function} fn Callback function to run when data is obtained
4099 */
4100 function _fnBuildAjax( oSettings, data, fn )
4101 {
4102 var ajaxData;
4103 var ajax = oSettings.ajax;
4104 var instance = oSettings.oInstance;
4105 var callback = function ( json ) {
4106 var status = oSettings.jqXHR
4107 ? oSettings.jqXHR.status
4108 : null;
4109
4110 if ( json === null || (typeof status === 'number' && status == 204 ) ) {
4111 json = {};
4112 _fnAjaxDataSrc( oSettings, json, [] );
4113 }
4114
4115 var error = json.error || json.sError;
4116 if ( error ) {
4117 _fnLog( oSettings, 0, error );
4118 }
4119
4120 // Microsoft often wrap JSON as a string in another JSON object
4121 // Let's handle that automatically
4122 if (json.d && typeof json.d === 'string') {
4123 try {
4124 json = JSON.parse(json.d);
4125 }
4126 catch (e) {
4127 // noop
4128 }
4129 }
4130
4131 oSettings.json = json;
4132
4133 _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true );
4134 fn( json );
4135 };
4136
4137 if ( $.isPlainObject( ajax ) && ajax.data )
4138 {
4139 ajaxData = ajax.data;
4140
4141 var newData = typeof ajaxData === 'function' ?
4142 ajaxData( data, oSettings ) : // fn can manipulate data or return
4143 ajaxData; // an object object or array to merge
4144
4145 // If the function returned something, use that alone
4146 data = typeof ajaxData === 'function' && newData ?
4147 newData :
4148 $.extend( true, data, newData );
4149
4150 // Remove the data property as we've resolved it already and don't want
4151 // jQuery to do it again (it is restored at the end of the function)
4152 delete ajax.data;
4153 }
4154
4155 var baseAjax = {
4156 "url": typeof ajax === 'string' ?
4157 ajax :
4158 '',
4159 "data": data,
4160 "success": callback,
4161 "dataType": "json",
4162 "cache": false,
4163 "type": oSettings.sServerMethod,
4164 "error": function (xhr, error) {
4165 var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR], true );
4166
4167 if ( ret.indexOf(true) === -1 ) {
4168 if ( error == "parsererror" ) {
4169 _fnLog( oSettings, 0, 'Invalid JSON response', 1 );
4170 }
4171 else if ( xhr.readyState === 4 ) {
4172 _fnLog( oSettings, 0, 'Ajax error', 7 );
4173 }
4174 }
4175
4176 _fnProcessingDisplay( oSettings, false );
4177 }
4178 };
4179
4180 // If `ajax` option is an object, extend and override our default base
4181 if ( $.isPlainObject( ajax ) ) {
4182 $.extend( baseAjax, ajax )
4183 }
4184
4185 // Store the data submitted for the API
4186 oSettings.oAjaxData = data;
4187
4188 // Allow plug-ins and external processes to modify the data
4189 _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true );
4190
4191 if ( typeof ajax === 'function' )
4192 {
4193 // Is a function - let the caller define what needs to be done
4194 oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
4195 }
4196 else if (ajax.url === '') {
4197 // No url, so don't load any data. Just apply an empty data array
4198 // to the object for the callback.
4199 var empty = {};
4200
4201 DataTable.util.set(ajax.dataSrc)(empty, []);
4202 callback(empty);
4203 }
4204 else {
4205 // Object to extend the base settings
4206 oSettings.jqXHR = $.ajax( baseAjax );
4207 }
4208
4209 // Restore for next time around
4210 if ( ajaxData ) {
4211 ajax.data = ajaxData;
4212 }
4213 }
4214
4215
4216 /**
4217 * Update the table using an Ajax call
4218 * @param {object} settings dataTables settings object
4219 * @returns {boolean} Block the table drawing or not
4220 * @memberof DataTable#oApi
4221 */
4222 function _fnAjaxUpdate( settings )
4223 {
4224 settings.iDraw++;
4225 _fnProcessingDisplay( settings, true );
4226
4227 _fnBuildAjax(
4228 settings,
4229 _fnAjaxParameters( settings ),
4230 function(json) {
4231 _fnAjaxUpdateDraw( settings, json );
4232 }
4233 );
4234 }
4235
4236
4237 /**
4238 * Build up the parameters in an object needed for a server-side processing
4239 * request.
4240 * @param {object} oSettings dataTables settings object
4241 * @returns {bool} block the table drawing or not
4242 * @memberof DataTable#oApi
4243 */
4244 function _fnAjaxParameters( settings )
4245 {
4246 var
4247 columns = settings.aoColumns,
4248 features = settings.oFeatures,
4249 preSearch = settings.oPreviousSearch,
4250 preColSearch = settings.aoPreSearchCols,
4251 colData = function ( idx, prop ) {
4252 return typeof columns[idx][prop] === 'function' ?
4253 'function' :
4254 columns[idx][prop];
4255 };
4256
4257 return {
4258 draw: settings.iDraw,
4259 columns: columns.map( function ( column, i ) {
4260 return {
4261 data: colData(i, 'mData'),
4262 name: column.sName,
4263 searchable: column.bSearchable,
4264 orderable: column.bSortable,
4265 search: {
4266 value: preColSearch[i].search,
4267 regex: preColSearch[i].regex,
4268 fixed: Object.keys(column.searchFixed).map( function(name) {
4269 return {
4270 name: name,
4271 term: column.searchFixed[name].toString()
4272 }
4273 })
4274 }
4275 };
4276 } ),
4277 order: _fnSortFlatten( settings ).map( function ( val ) {
4278 return {
4279 column: val.col,
4280 dir: val.dir,
4281 name: colData(val.col, 'sName')
4282 };
4283 } ),
4284 start: settings._iDisplayStart,
4285 length: features.bPaginate ?
4286 settings._iDisplayLength :
4287 -1,
4288 search: {
4289 value: preSearch.search,
4290 regex: preSearch.regex,
4291 fixed: Object.keys(settings.searchFixed).map( function(name) {
4292 return {
4293 name: name,
4294 term: settings.searchFixed[name].toString()
4295 }
4296 })
4297 }
4298 };
4299 }
4300
4301
4302 /**
4303 * Data the data from the server (nuking the old) and redraw the table
4304 * @param {object} oSettings dataTables settings object
4305 * @param {object} json json data return from the server.
4306 * @param {string} json.sEcho Tracking flag for DataTables to match requests
4307 * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
4308 * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
4309 * @param {array} json.aaData The data to display on this page
4310 * @param {string} [json.sColumns] Column ordering (sName, comma separated)
4311 * @memberof DataTable#oApi
4312 */
4313 function _fnAjaxUpdateDraw ( settings, json )
4314 {
4315 var data = _fnAjaxDataSrc(settings, json);
4316 var draw = _fnAjaxDataSrcParam(settings, 'draw', json);
4317 var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json);
4318 var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json);
4319
4320 if ( draw !== undefined ) {
4321 // Protect against out of sequence returns
4322 if ( draw*1 < settings.iDraw ) {
4323 return;
4324 }
4325 settings.iDraw = draw * 1;
4326 }
4327
4328 // No data in returned object, so rather than an array, we show an empty table
4329 if ( ! data ) {
4330 data = [];
4331 }
4332
4333 _fnClearTable( settings );
4334 settings._iRecordsTotal = parseInt(recordsTotal, 10);
4335 settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
4336
4337 for ( var i=0, ien=data.length ; i<ien ; i++ ) {
4338 _fnAddData( settings, data[i] );
4339 }
4340 settings.aiDisplay = settings.aiDisplayMaster.slice();
4341
4342 _fnColumnTypes(settings);
4343 _fnDraw( settings, true );
4344 _fnInitComplete( settings );
4345 _fnProcessingDisplay( settings, false );
4346 }
4347
4348
4349 /**
4350 * Get the data from the JSON data source to use for drawing a table. Using
4351 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
4352 * source object, or from a processing function.
4353 * @param {object} settings dataTables settings object
4354 * @param {object} json Data source object / array from the server
4355 * @return {array} Array of data to use
4356 */
4357 function _fnAjaxDataSrc ( settings, json, write )
4358 {
4359 var dataProp = 'data';
4360
4361 if ($.isPlainObject( settings.ajax ) && settings.ajax.dataSrc !== undefined) {
4362 // Could in inside a `dataSrc` object, or not!
4363 var dataSrc = settings.ajax.dataSrc;
4364
4365 // string, function and object are valid types
4366 if (typeof dataSrc === 'string' || typeof dataSrc === 'function') {
4367 dataProp = dataSrc;
4368 }
4369 else if (dataSrc.data !== undefined) {
4370 dataProp = dataSrc.data;
4371 }
4372 }
4373
4374 if ( ! write ) {
4375 if ( dataProp === 'data' ) {
4376 // If the default, then we still want to support the old style, and safely ignore
4377 // it if possible
4378 return json.aaData || json[dataProp];
4379 }
4380
4381 return dataProp !== "" ?
4382 _fnGetObjectDataFn( dataProp )( json ) :
4383 json;
4384 }
4385
4386 // set
4387 _fnSetObjectDataFn( dataProp )( json, write );
4388 }
4389
4390 /**
4391 * Very similar to _fnAjaxDataSrc, but for the other SSP properties
4392 * @param {*} settings DataTables settings object
4393 * @param {*} param Target parameter
4394 * @param {*} json JSON data
4395 * @returns Resolved value
4396 */
4397 function _fnAjaxDataSrcParam (settings, param, json) {
4398 var dataSrc = $.isPlainObject( settings.ajax )
4399 ? settings.ajax.dataSrc
4400 : null;
4401
4402 if (dataSrc && dataSrc[param]) {
4403 // Get from custom location
4404 return _fnGetObjectDataFn( dataSrc[param] )( json );
4405 }
4406
4407 // else - Default behaviour
4408 var old = '';
4409
4410 // Legacy support
4411 if (param === 'draw') {
4412 old = 'sEcho';
4413 }
4414 else if (param === 'recordsTotal') {
4415 old = 'iTotalRecords';
4416 }
4417 else if (param === 'recordsFiltered') {
4418 old = 'iTotalDisplayRecords';
4419 }
4420
4421 return json[old] !== undefined
4422 ? json[old]
4423 : json[param];
4424 }
4425
4426
4427 /**
4428 * Filter the table using both the global filter and column based filtering
4429 * @param {object} settings dataTables settings object
4430 * @param {object} input search information
4431 * @memberof DataTable#oApi
4432 */
4433 function _fnFilterComplete ( settings, input )
4434 {
4435 var columnsSearch = settings.aoPreSearchCols;
4436
4437 // In server-side processing all filtering is done by the server, so no point hanging around here
4438 if ( _fnDataSource( settings ) != 'ssp' )
4439 {
4440 // Check if any of the rows were invalidated
4441 _fnFilterData( settings );
4442
4443 // Start from the full data set
4444 settings.aiDisplay = settings.aiDisplayMaster.slice();
4445
4446 // Global filter first
4447 _fnFilter( settings.aiDisplay, settings, input.search, input );
4448
4449 $.each(settings.searchFixed, function (name, term) {
4450 _fnFilter(settings.aiDisplay, settings, term, {});
4451 });
4452
4453 // Then individual column filters
4454 for ( var i=0 ; i<columnsSearch.length ; i++ )
4455 {
4456 var col = columnsSearch[i];
4457
4458 _fnFilter(
4459 settings.aiDisplay,
4460 settings,
4461 col.search,
4462 col,
4463 i
4464 );
4465
4466 $.each(settings.aoColumns[i].searchFixed, function (name, term) {
4467 _fnFilter(settings.aiDisplay, settings, term, {}, i);
4468 });
4469 }
4470
4471 // And finally global filtering
4472 _fnFilterCustom( settings );
4473 }
4474
4475 // Tell the draw function we have been filtering
4476 settings.bFiltered = true;
4477
4478 _fnCallbackFire( settings, null, 'search', [settings] );
4479 }
4480
4481
4482 /**
4483 * Apply custom filtering functions
4484 *
4485 * This is legacy now that we have named functions, but it is widely used
4486 * from 1.x, so it is not yet deprecated.
4487 * @param {object} oSettings dataTables settings object
4488 * @memberof DataTable#oApi
4489 */
4490 function _fnFilterCustom( settings )
4491 {
4492 var filters = DataTable.ext.search;
4493 var displayRows = settings.aiDisplay;
4494 var row, rowIdx;
4495
4496 for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
4497 var rows = [];
4498
4499 // Loop over each row and see if it should be included
4500 for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
4501 rowIdx = displayRows[ j ];
4502 row = settings.aoData[ rowIdx ];
4503
4504 if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
4505 rows.push( rowIdx );
4506 }
4507 }
4508
4509 // So the array reference doesn't break set the results into the
4510 // existing array
4511 displayRows.length = 0;
4512 _fnArrayApply(displayRows, rows);
4513 }
4514 }
4515
4516
4517 /**
4518 * Filter the data table based on user input and draw the table
4519 */
4520 function _fnFilter( searchRows, settings, input, options, column )
4521 {
4522 if ( input === '' ) {
4523 return;
4524 }
4525
4526 var i = 0;
4527 var matched = [];
4528
4529 // Search term can be a function, regex or string - if a string we apply our
4530 // smart filtering regex (assuming the options require that)
4531 var searchFunc = typeof input === 'function' ? input : null;
4532 var rpSearch = input instanceof RegExp
4533 ? input
4534 : searchFunc
4535 ? null
4536 : _fnFilterCreateSearch( input, options );
4537
4538 // Then for each row, does the test pass. If not, lop the row from the array
4539 for (i=0 ; i<searchRows.length ; i++) {
4540 var row = settings.aoData[ searchRows[i] ];
4541 var data = column === undefined
4542 ? row._sFilterRow
4543 : row._aFilterData[ column ];
4544
4545 if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) {
4546 matched.push(searchRows[i]);
4547 }
4548 }
4549
4550 // Mutate the searchRows array
4551 searchRows.length = matched.length;
4552
4553 for (i=0 ; i<matched.length ; i++) {
4554 searchRows[i] = matched[i];
4555 }
4556 }
4557
4558
4559 /**
4560 * Build a regular expression object suitable for searching a table
4561 * @param {string} sSearch string to search for
4562 * @param {bool} bRegex treat as a regular expression or not
4563 * @param {bool} bSmart perform smart filtering or not
4564 * @param {bool} bCaseInsensitive Do case insensitive matching or not
4565 * @returns {RegExp} constructed object
4566 * @memberof DataTable#oApi
4567 */
4568 function _fnFilterCreateSearch( search, inOpts )
4569 {
4570 var not = [];
4571 var options = $.extend({}, {
4572 boundary: false,
4573 caseInsensitive: true,
4574 exact: false,
4575 regex: false,
4576 smart: true
4577 }, inOpts);
4578
4579 if (typeof search !== 'string') {
4580 search = search.toString();
4581 }
4582
4583 // Remove diacritics if normalize is set up to do so
4584 search = _normalize(search);
4585
4586 if (options.exact) {
4587 return new RegExp(
4588 '^'+_fnEscapeRegex(search)+'$',
4589 options.caseInsensitive ? 'i' : ''
4590 );
4591 }
4592
4593 search = options.regex ?
4594 search :
4595 _fnEscapeRegex( search );
4596
4597 if ( options.smart ) {
4598 /* For smart filtering we want to allow the search to work regardless of
4599 * word order. We also want double quoted text to be preserved, so word
4600 * order is important - a la google. And a negative look around for
4601 * finding rows which don't contain a given string.
4602 *
4603 * So this is the sort of thing we want to generate:
4604 *
4605 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
4606 */
4607 var parts = search.match( /!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''];
4608 var a = parts.map( function ( word ) {
4609 var negative = false;
4610 var m;
4611
4612 // Determine if it is a "does not include"
4613 if ( word.charAt(0) === '!' ) {
4614 negative = true;
4615 word = word.substring(1);
4616 }
4617
4618 // Strip the quotes from around matched phrases
4619 if ( word.charAt(0) === '"' ) {
4620 m = word.match( /^"(.*)"$/ );
4621 word = m ? m[1] : word;
4622 }
4623 else if ( word.charAt(0) === '\u201C' ) {
4624 // Smart quote match (iPhone users)
4625 m = word.match( /^\u201C(.*)\u201D$/ );
4626 word = m ? m[1] : word;
4627 }
4628
4629 // For our "not" case, we need to modify the string that is
4630 // allowed to match at the end of the expression.
4631 if (negative) {
4632 if (word.length > 1) {
4633 not.push('(?!'+word+')');
4634 }
4635
4636 word = '';
4637 }
4638
4639 return word.replace(/"/g, '');
4640 } );
4641
4642 var match = not.length
4643 ? not.join('')
4644 : '';
4645
4646 var boundary = options.boundary
4647 ? '\\b'
4648 : '';
4649
4650 search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$';
4651 }
4652
4653 return new RegExp( search, options.caseInsensitive ? 'i' : '' );
4654 }
4655
4656
4657 /**
4658 * Escape a string such that it can be used in a regular expression
4659 * @param {string} sVal string to escape
4660 * @returns {string} escaped string
4661 * @memberof DataTable#oApi
4662 */
4663 var _fnEscapeRegex = DataTable.util.escapeRegex;
4664
4665 var __filter_div = $('<div>')[0];
4666 var __filter_div_textContent = __filter_div.textContent !== undefined;
4667
4668 // Update the filtering data for each row if needed (by invalidation or first run)
4669 function _fnFilterData ( settings )
4670 {
4671 var columns = settings.aoColumns;
4672 var data = settings.aoData;
4673 var column;
4674 var j, jen, filterData, cellData, row;
4675 var wasInvalidated = false;
4676
4677 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
4678 if (! data[rowIdx]) {
4679 continue;
4680 }
4681
4682 row = data[rowIdx];
4683
4684 if ( ! row._aFilterData ) {
4685 filterData = [];
4686
4687 for ( j=0, jen=columns.length ; j<jen ; j++ ) {
4688 column = columns[j];
4689
4690 if ( column.bSearchable ) {
4691 cellData = _fnGetCellData( settings, rowIdx, j, 'filter' );
4692
4693 // Search in DataTables is string based
4694 if ( cellData === null ) {
4695 cellData = '';
4696 }
4697
4698 if ( typeof cellData !== 'string' && cellData.toString ) {
4699 cellData = cellData.toString();
4700 }
4701 }
4702 else {
4703 cellData = '';
4704 }
4705
4706 // If it looks like there is an HTML entity in the string,
4707 // attempt to decode it so sorting works as expected. Note that
4708 // we could use a single line of jQuery to do this, but the DOM
4709 // method used here is much faster https://jsperf.com/html-decode
4710 if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
4711 __filter_div.innerHTML = cellData;
4712 cellData = __filter_div_textContent ?
4713 __filter_div.textContent :
4714 __filter_div.innerText;
4715 }
4716
4717 if ( cellData.replace ) {
4718 cellData = cellData.replace(/[\r\n\u2028]/g, '');
4719 }
4720
4721 filterData.push( cellData );
4722 }
4723
4724 row._aFilterData = filterData;
4725 row._sFilterRow = filterData.join(' ');
4726 wasInvalidated = true;
4727 }
4728 }
4729
4730 return wasInvalidated;
4731 }
4732
4733
4734 /**
4735 * Draw the table for the first time, adding all required features
4736 * @param {object} settings dataTables settings object
4737 * @memberof DataTable#oApi
4738 */
4739 function _fnInitialise ( settings )
4740 {
4741 var i;
4742 var init = settings.oInit;
4743 var deferLoading = settings.deferLoading;
4744 var dataSrc = _fnDataSource( settings );
4745
4746 // Ensure that the table data is fully initialised
4747 if ( ! settings.bInitialised ) {
4748 setTimeout( function(){ _fnInitialise( settings ); }, 200 );
4749 return;
4750 }
4751
4752 // Build the header / footer for the table
4753 _fnBuildHead( settings, 'header' );
4754 _fnBuildHead( settings, 'footer' );
4755
4756 // Load the table's state (if needed) and then render around it and draw
4757 _fnLoadState( settings, init, function () {
4758 // Then draw the header / footer
4759 _fnDrawHead( settings, settings.aoHeader );
4760 _fnDrawHead( settings, settings.aoFooter );
4761
4762 // Cache the paging start point, as the first redraw will reset it
4763 var iAjaxStart = settings.iInitDisplayStart
4764
4765 // Local data load
4766 // Check if there is data passing into the constructor
4767 if ( init.aaData ) {
4768 for ( i=0 ; i<init.aaData.length ; i++ ) {
4769 _fnAddData( settings, init.aaData[ i ] );
4770 }
4771 }
4772 else if ( deferLoading || dataSrc == 'dom' ) {
4773 // Grab the data from the page
4774 _fnAddTr( settings, $(settings.nTBody).children('tr') );
4775 }
4776
4777 // Filter not yet applied - copy the display master
4778 settings.aiDisplay = settings.aiDisplayMaster.slice();
4779
4780 // Enable features
4781 _fnAddOptionsHtml( settings );
4782 _fnSortInit( settings );
4783
4784 _colGroup( settings );
4785
4786 /* Okay to show that something is going on now */
4787 _fnProcessingDisplay( settings, true );
4788
4789 _fnCallbackFire( settings, null, 'preInit', [settings], true );
4790
4791 // If there is default sorting required - let's do it. The sort function
4792 // will do the drawing for us. Otherwise we draw the table regardless of the
4793 // Ajax source - this allows the table to look initialised for Ajax sourcing
4794 // data (show 'loading' message possibly)
4795 _fnReDraw( settings );
4796
4797 // Server-side processing init complete is done by _fnAjaxUpdateDraw
4798 if ( dataSrc != 'ssp' || deferLoading ) {
4799 // if there is an ajax source load the data
4800 if ( dataSrc == 'ajax' ) {
4801 _fnBuildAjax( settings, {}, function(json) {
4802 var aData = _fnAjaxDataSrc( settings, json );
4803
4804 // Got the data - add it to the table
4805 for ( i=0 ; i<aData.length ; i++ ) {
4806 _fnAddData( settings, aData[i] );
4807 }
4808
4809 // Reset the init display for cookie saving. We've already done
4810 // a filter, and therefore cleared it before. So we need to make
4811 // it appear 'fresh'
4812 settings.iInitDisplayStart = iAjaxStart;
4813
4814 _fnReDraw( settings );
4815 _fnProcessingDisplay( settings, false );
4816 _fnInitComplete( settings );
4817 }, settings );
4818 }
4819 else {
4820 _fnInitComplete( settings );
4821 _fnProcessingDisplay( settings, false );
4822 }
4823 }
4824 } );
4825 }
4826
4827
4828 /**
4829 * Draw the table for the first time, adding all required features
4830 * @param {object} settings dataTables settings object
4831 * @memberof DataTable#oApi
4832 */
4833 function _fnInitComplete ( settings )
4834 {
4835 if (settings._bInitComplete) {
4836 return;
4837 }
4838
4839 var args = [settings, settings.json];
4840
4841 settings._bInitComplete = true;
4842
4843 // Table is fully set up and we have data, so calculate the
4844 // column widths
4845 _fnAdjustColumnSizing( settings );
4846
4847 _fnCallbackFire( settings, null, 'plugin-init', args, true );
4848 _fnCallbackFire( settings, 'aoInitComplete', 'init', args, true );
4849 }
4850
4851 function _fnLengthChange ( settings, val )
4852 {
4853 var len = parseInt( val, 10 );
4854 settings._iDisplayLength = len;
4855
4856 _fnLengthOverflow( settings );
4857
4858 // Fire length change event
4859 _fnCallbackFire( settings, null, 'length', [settings, len] );
4860 }
4861
4862 /**
4863 * Alter the display settings to change the page
4864 * @param {object} settings DataTables settings object
4865 * @param {string|int} action Paging action to take: "first", "previous",
4866 * "next" or "last" or page number to jump to (integer)
4867 * @param [bool] redraw Automatically draw the update or not
4868 * @returns {bool} true page has changed, false - no change
4869 * @memberof DataTable#oApi
4870 */
4871 function _fnPageChange ( settings, action, redraw )
4872 {
4873 var
4874 start = settings._iDisplayStart,
4875 len = settings._iDisplayLength,
4876 records = settings.fnRecordsDisplay();
4877
4878 if ( records === 0 || len === -1 )
4879 {
4880 start = 0;
4881 }
4882 else if ( typeof action === "number" )
4883 {
4884 start = action * len;
4885
4886 if ( start > records )
4887 {
4888 start = 0;
4889 }
4890 }
4891 else if ( action == "first" )
4892 {
4893 start = 0;
4894 }
4895 else if ( action == "previous" )
4896 {
4897 start = len >= 0 ?
4898 start - len :
4899 0;
4900
4901 if ( start < 0 )
4902 {
4903 start = 0;
4904 }
4905 }
4906 else if ( action == "next" )
4907 {
4908 if ( start + len < records )
4909 {
4910 start += len;
4911 }
4912 }
4913 else if ( action == "last" )
4914 {
4915 start = Math.floor( (records-1) / len) * len;
4916 }
4917 else if ( action === 'ellipsis' )
4918 {
4919 return;
4920 }
4921 else
4922 {
4923 _fnLog( settings, 0, "Unknown paging action: "+action, 5 );
4924 }
4925
4926 var changed = settings._iDisplayStart !== start;
4927 settings._iDisplayStart = start;
4928
4929 _fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] );
4930
4931 if ( changed && redraw ) {
4932 _fnDraw( settings );
4933 }
4934
4935 return changed;
4936 }
4937
4938
4939 /**
4940 * Generate the node required for the processing node
4941 * @param {object} settings DataTables settings object
4942 */
4943 function _processingHtml ( settings )
4944 {
4945 var table = settings.nTable;
4946 var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== '';
4947
4948 if ( settings.oFeatures.bProcessing ) {
4949 var n = $('<div/>', {
4950 'id': settings.sTableId + '_processing',
4951 'class': settings.oClasses.processing.container,
4952 'role': 'status'
4953 } )
4954 .html( settings.oLanguage.sProcessing )
4955 .append('<div><div></div><div></div><div></div><div></div></div>');
4956
4957 // Different positioning depending on if scrolling is enabled or not
4958 if (scrolling) {
4959 n.prependTo( $('div.dt-scroll', settings.nTableWrapper) );
4960 }
4961 else {
4962 n.insertBefore( table );
4963 }
4964
4965 $(table).on( 'processing.dt.DT', function (e, s, show) {
4966 n.css( 'display', show ? 'block' : 'none' );
4967 } );
4968 }
4969 }
4970
4971
4972 /**
4973 * Display or hide the processing indicator
4974 * @param {object} settings DataTables settings object
4975 * @param {bool} show Show the processing indicator (true) or not (false)
4976 */
4977 function _fnProcessingDisplay ( settings, show )
4978 {
4979 // Ignore cases when we are still redrawing
4980 if (settings.bDrawing && show === false) {
4981 return;
4982 }
4983
4984 _fnCallbackFire( settings, null, 'processing', [settings, show] );
4985 }
4986
4987 /**
4988 * Show the processing element if an action takes longer than a given time
4989 *
4990 * @param {*} settings DataTables settings object
4991 * @param {*} enable Do (true) or not (false) async processing (local feature enablement)
4992 * @param {*} run Function to run
4993 */
4994 function _fnProcessingRun( settings, enable, run ) {
4995 if (! enable) {
4996 // Immediate execution, synchronous
4997 run();
4998 }
4999 else {
5000 _fnProcessingDisplay(settings, true);
5001
5002 // Allow the processing display to show if needed
5003 setTimeout(function () {
5004 run();
5005
5006 _fnProcessingDisplay(settings, false);
5007 }, 0);
5008 }
5009 }
5010 /**
5011 * Add any control elements for the table - specifically scrolling
5012 * @param {object} settings dataTables settings object
5013 * @returns {node} Node to add to the DOM
5014 * @memberof DataTable#oApi
5015 */
5016 function _fnFeatureHtmlTable ( settings )
5017 {
5018 var table = $(settings.nTable);
5019
5020 // Scrolling from here on in
5021 var scroll = settings.oScroll;
5022
5023 if ( scroll.sX === '' && scroll.sY === '' ) {
5024 return settings.nTable;
5025 }
5026
5027 var scrollX = scroll.sX;
5028 var scrollY = scroll.sY;
5029 var classes = settings.oClasses.scrolling;
5030 var caption = settings.captionNode;
5031 var captionSide = caption ? caption._captionSide : null;
5032 var headerClone = $( table[0].cloneNode(false) );
5033 var footerClone = $( table[0].cloneNode(false) );
5034 var footer = table.children('tfoot');
5035 var _div = '<div/>';
5036 var size = function ( s ) {
5037 return !s ? null : _fnStringToCss( s );
5038 };
5039
5040 if ( ! footer.length ) {
5041 footer = null;
5042 }
5043
5044 /*
5045 * The HTML structure that we want to generate in this function is:
5046 * div - scroller
5047 * div - scroll head
5048 * div - scroll head inner
5049 * table - scroll head table
5050 * thead - thead
5051 * div - scroll body
5052 * table - table (master table)
5053 * thead - thead clone for sizing
5054 * tbody - tbody
5055 * div - scroll foot
5056 * div - scroll foot inner
5057 * table - scroll foot table
5058 * tfoot - tfoot
5059 */
5060 var scroller = $( _div, { 'class': classes.container } )
5061 .append(
5062 $(_div, { 'class': classes.header.self } )
5063 .css( {
5064 overflow: 'hidden',
5065 position: 'relative',
5066 border: 0,
5067 width: scrollX ? size(scrollX) : '100%'
5068 } )
5069 .append(
5070 $(_div, { 'class': classes.header.inner } )
5071 .css( {
5072 'box-sizing': 'content-box',
5073 width: scroll.sXInner || '100%'
5074 } )
5075 .append(
5076 headerClone
5077 .removeAttr('id')
5078 .css( 'margin-left', 0 )
5079 .append( captionSide === 'top' ? caption : null )
5080 .append(
5081 table.children('thead')
5082 )
5083 )
5084 )
5085 )
5086 .append(
5087 $(_div, { 'class': classes.body } )
5088 .css( {
5089 position: 'relative',
5090 overflow: 'auto',
5091 width: size( scrollX )
5092 } )
5093 .append( table )
5094 );
5095
5096 if ( footer ) {
5097 scroller.append(
5098 $(_div, { 'class': classes.footer.self } )
5099 .css( {
5100 overflow: 'hidden',
5101 border: 0,
5102 width: scrollX ? size(scrollX) : '100%'
5103 } )
5104 .append(
5105 $(_div, { 'class': classes.footer.inner } )
5106 .append(
5107 footerClone
5108 .removeAttr('id')
5109 .css( 'margin-left', 0 )
5110 .append( captionSide === 'bottom' ? caption : null )
5111 .append(
5112 table.children('tfoot')
5113 )
5114 )
5115 )
5116 );
5117 }
5118
5119 var children = scroller.children();
5120 var scrollHead = children[0];
5121 var scrollBody = children[1];
5122 var scrollFoot = footer ? children[2] : null;
5123
5124 // When the body is scrolled, then we also want to scroll the headers
5125 $(scrollBody).on( 'scroll.DT', function () {
5126 var scrollLeft = this.scrollLeft;
5127
5128 scrollHead.scrollLeft = scrollLeft;
5129
5130 if ( footer ) {
5131 scrollFoot.scrollLeft = scrollLeft;
5132 }
5133 } );
5134
5135 // When focus is put on the header cells, we might need to scroll the body
5136 $('th, td', scrollHead).on('focus', function () {
5137 var scrollLeft = scrollHead.scrollLeft;
5138
5139 scrollBody.scrollLeft = scrollLeft;
5140
5141 if ( footer ) {
5142 scrollBody.scrollLeft = scrollLeft;
5143 }
5144 });
5145
5146 $(scrollBody).css('max-height', scrollY);
5147 if (! scroll.bCollapse) {
5148 $(scrollBody).css('height', scrollY);
5149 }
5150
5151 settings.nScrollHead = scrollHead;
5152 settings.nScrollBody = scrollBody;
5153 settings.nScrollFoot = scrollFoot;
5154
5155 // On redraw - align columns
5156 settings.aoDrawCallback.push(_fnScrollDraw);
5157
5158 return scroller[0];
5159 }
5160
5161
5162
5163 /**
5164 * Update the header, footer and body tables for resizing - i.e. column
5165 * alignment.
5166 *
5167 * Welcome to the most horrible function DataTables. The process that this
5168 * function follows is basically:
5169 * 1. Re-create the table inside the scrolling div
5170 * 2. Correct colgroup > col values if needed
5171 * 3. Copy colgroup > col over to header and footer
5172 * 4. Clean up
5173 *
5174 * @param {object} settings dataTables settings object
5175 * @memberof DataTable#oApi
5176 */
5177 function _fnScrollDraw ( settings )
5178 {
5179 // Given that this is such a monster function, a lot of variables are use
5180 // to try and keep the minimised size as small as possible
5181 var
5182 scroll = settings.oScroll,
5183 barWidth = scroll.iBarWidth,
5184 divHeader = $(settings.nScrollHead),
5185 divHeaderInner = divHeader.children('div'),
5186 divHeaderTable = divHeaderInner.children('table'),
5187 divBodyEl = settings.nScrollBody,
5188 divBody = $(divBodyEl),
5189 divFooter = $(settings.nScrollFoot),
5190 divFooterInner = divFooter.children('div'),
5191 divFooterTable = divFooterInner.children('table'),
5192 header = $(settings.nTHead),
5193 table = $(settings.nTable),
5194 footer = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null,
5195 browser = settings.oBrowser,
5196 headerCopy, footerCopy;
5197
5198 // If the scrollbar visibility has changed from the last draw, we need to
5199 // adjust the column sizes as the table width will have changed to account
5200 // for the scrollbar
5201 var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
5202
5203 if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
5204 settings.scrollBarVis = scrollBarVis;
5205 _fnAdjustColumnSizing( settings );
5206 return; // adjust column sizing will call this function again
5207 }
5208 else {
5209 settings.scrollBarVis = scrollBarVis;
5210 }
5211
5212 // 1. Re-create the table inside the scrolling div
5213 // Remove the old minimised thead and tfoot elements in the inner table
5214 table.children('thead, tfoot').remove();
5215
5216 // Clone the current header and footer elements and then place it into the inner table
5217 headerCopy = header.clone().prependTo( table );
5218 headerCopy.find('th, td').removeAttr('tabindex');
5219 headerCopy.find('[id]').removeAttr('id');
5220
5221 if ( footer ) {
5222 footerCopy = footer.clone().prependTo( table );
5223 footerCopy.find('[id]').removeAttr('id');
5224 }
5225
5226 // 2. Correct colgroup > col values if needed
5227 // It is possible that the cell sizes are smaller than the content, so we need to
5228 // correct colgroup>col for such cases. This can happen if the auto width detection
5229 // uses a cell which has a longer string, but isn't the widest! For example
5230 // "Chief Executive Officer (CEO)" is the longest string in the demo, but
5231 // "Systems Administrator" is actually the widest string since it doesn't collapse.
5232 // Note the use of translating into a column index to get the `col` element. This
5233 // is because of Responsive which might remove `col` elements, knocking the alignment
5234 // of the indexes out.
5235 if (settings.aiDisplay.length) {
5236 // Get the column sizes from the first row in the table. This should really be a
5237 // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year
5238 // browser support
5239 var firstTr = null;
5240 var start = _fnDataSource( settings ) !== 'ssp'
5241 ? settings._iDisplayStart
5242 : 0;
5243
5244 for (i=start ; i<start + settings.aiDisplay.length ; i++) {
5245 var idx = settings.aiDisplay[i];
5246 var tr = settings.aoData[idx].nTr;
5247
5248 if (tr) {
5249 firstTr = tr;
5250 break;
5251 }
5252 }
5253
5254 if (firstTr) {
5255 var colSizes = $(firstTr).children('th, td').map(function (vis) {
5256 return {
5257 idx: _fnVisibleToColumnIndex(settings, vis),
5258 width: $(this).outerWidth()
5259 };
5260 });
5261
5262 // Check against what the colgroup > col is set to and correct if needed
5263 for (var i=0 ; i<colSizes.length ; i++) {
5264 var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
5265 var colWidth = colEl.style.width.replace('px', '');
5266
5267 if (colWidth !== colSizes[i].width) {
5268 colEl.style.width = colSizes[i].width + 'px';
5269
5270 if (scroll.sX) {
5271 colEl.style.minWidth = colSizes[i].width + 'px';
5272 }
5273 }
5274 }
5275 }
5276 }
5277
5278 // 3. Copy the colgroup over to the header and footer
5279 divHeaderTable
5280 .find('colgroup')
5281 .remove();
5282
5283 divHeaderTable.append(settings.colgroup.clone());
5284
5285 if ( footer ) {
5286 divFooterTable
5287 .find('colgroup')
5288 .remove();
5289
5290 divFooterTable.append(settings.colgroup.clone());
5291 }
5292
5293 // "Hide" the header and footer that we used for the sizing. We need to keep
5294 // the content of the cell so that the width applied to the header and body
5295 // both match, but we want to hide it completely.
5296 $('th, td', headerCopy).each(function () {
5297 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
5298 });
5299
5300 if ( footer ) {
5301 $('th, td', footerCopy).each(function () {
5302 $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
5303 });
5304 }
5305
5306 // 4. Clean up
5307 // Figure out if there are scrollbar present - if so then we need a the header and footer to
5308 // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
5309 var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
5310 var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
5311
5312 // Set the width's of the header and footer tables
5313 var outerWidth = table.outerWidth();
5314
5315 divHeaderTable.css('width', _fnStringToCss( outerWidth ));
5316 divHeaderInner
5317 .css('width', _fnStringToCss( outerWidth ))
5318 .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
5319
5320 if ( footer ) {
5321 divFooterTable.css('width', _fnStringToCss( outerWidth ));
5322 divFooterInner
5323 .css('width', _fnStringToCss( outerWidth ))
5324 .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
5325 }
5326
5327 // Correct DOM ordering for colgroup - comes before the thead
5328 table.children('colgroup').prependTo(table);
5329
5330 // Adjust the position of the header in case we loose the y-scrollbar
5331 divBody.trigger('scroll');
5332
5333 // If sorting or filtering has occurred, jump the scrolling back to the top
5334 // only if we aren't holding the position
5335 if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
5336 divBodyEl.scrollTop = 0;
5337 }
5338 }
5339
5340 /**
5341 * Calculate the width of columns for the table
5342 * @param {object} settings dataTables settings object
5343 * @memberof DataTable#oApi
5344 */
5345 function _fnCalculateColumnWidths ( settings )
5346 {
5347 // Not interested in doing column width calculation if auto-width is disabled
5348 if (! settings.oFeatures.bAutoWidth) {
5349 return;
5350 }
5351
5352 var
5353 table = settings.nTable,
5354 columns = settings.aoColumns,
5355 scroll = settings.oScroll,
5356 scrollY = scroll.sY,
5357 scrollX = scroll.sX,
5358 scrollXInner = scroll.sXInner,
5359 visibleColumns = _fnGetColumns( settings, 'bVisible' ),
5360 tableWidthAttr = table.getAttribute('width'), // from DOM element
5361 tableContainer = table.parentNode,
5362 i, column, columnIdx;
5363
5364 var styleWidth = table.style.width;
5365 var containerWidth = _fnWrapperWidth(settings);
5366
5367 // Don't re-run for the same width as the last time
5368 if (containerWidth === settings.containerWidth) {
5369 return false;
5370 }
5371
5372 settings.containerWidth = containerWidth;
5373
5374 // If there is no width applied as a CSS style or as an attribute, we assume that
5375 // the width is intended to be 100%, which is usually is in CSS, but it is very
5376 // difficult to correctly parse the rules to get the final result.
5377 if ( ! styleWidth && ! tableWidthAttr) {
5378 table.style.width = '100%';
5379 styleWidth = '100%';
5380 }
5381
5382 if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
5383 tableWidthAttr = styleWidth;
5384 }
5385
5386 // Let plug-ins know that we are doing a recalc, in case they have changed any of the
5387 // visible columns their own way (e.g. Responsive uses display:none).
5388 _fnCallbackFire(
5389 settings,
5390 null,
5391 'column-calc',
5392 {visible: visibleColumns},
5393 false
5394 );
5395
5396 // Construct a single row, worst case, table with the widest
5397 // node in the data, assign any user defined widths, then insert it into
5398 // the DOM and allow the browser to do all the hard work of calculating
5399 // table widths
5400 var tmpTable = $(table.cloneNode())
5401 .css( 'visibility', 'hidden' )
5402 .removeAttr( 'id' );
5403
5404 // Clean up the table body
5405 tmpTable.append('<tbody>')
5406 var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
5407
5408 // Clone the table header and footer - we can't use the header / footer
5409 // from the cloned table, since if scrolling is active, the table's
5410 // real header and footer are contained in different table tags
5411 tmpTable
5412 .append( $(settings.nTHead).clone() )
5413 .append( $(settings.nTFoot).clone() );
5414
5415 // Remove any assigned widths from the footer (from scrolling)
5416 tmpTable.find('tfoot th, tfoot td').css('width', '');
5417
5418 // Apply custom sizing to the cloned header
5419 tmpTable.find('thead th, thead td').each( function () {
5420 // Get the `width` from the header layout
5421 var width = _fnColumnsSumWidth( settings, this, true, false );
5422
5423 if ( width ) {
5424 this.style.width = width;
5425
5426 // For scrollX we need to force the column width otherwise the
5427 // browser will collapse it. If this width is smaller than the
5428 // width the column requires, then it will have no effect
5429 if ( scrollX ) {
5430 this.style.minWidth = width;
5431
5432 $( this ).append( $('<div/>').css( {
5433 width: width,
5434 margin: 0,
5435 padding: 0,
5436 border: 0,
5437 height: 1
5438 } ) );
5439 }
5440 }
5441 else {
5442 this.style.width = '';
5443 }
5444 } );
5445
5446 // Find the widest piece of data for each column and put it into the table
5447 for ( i=0 ; i<visibleColumns.length ; i++ ) {
5448 columnIdx = visibleColumns[i];
5449 column = columns[ columnIdx ];
5450
5451 var longest = _fnGetMaxLenString(settings, columnIdx);
5452 var autoClass = _ext.type.className[column.sType];
5453 var text = longest + column.sContentPadding;
5454 var insert = longest.indexOf('<') === -1
5455 ? document.createTextNode(text)
5456 : text
5457
5458 $('<td/>')
5459 .addClass(autoClass)
5460 .addClass(column.sClass)
5461 .append(insert)
5462 .appendTo(tr);
5463 }
5464
5465 // Tidy the temporary table - remove name attributes so there aren't
5466 // duplicated in the dom (radio elements for example)
5467 $('[name]', tmpTable).removeAttr('name');
5468
5469 // Table has been built, attach to the document so we can work with it.
5470 // A holding element is used, positioned at the top of the container
5471 // with minimal height, so it has no effect on if the container scrolls
5472 // or not. Otherwise it might trigger scrolling when it actually isn't
5473 // needed
5474 var holder = $('<div/>').css( scrollX || scrollY ?
5475 {
5476 position: 'absolute',
5477 top: 0,
5478 left: 0,
5479 height: 1,
5480 right: 0,
5481 overflow: 'hidden'
5482 } :
5483 {}
5484 )
5485 .append( tmpTable )
5486 .appendTo( tableContainer );
5487
5488 // When scrolling (X or Y) we want to set the width of the table as
5489 // appropriate. However, when not scrolling leave the table width as it
5490 // is. This results in slightly different, but I think correct behaviour
5491 if ( scrollX && scrollXInner ) {
5492 tmpTable.width( scrollXInner );
5493 }
5494 else if ( scrollX ) {
5495 tmpTable.css( 'width', 'auto' );
5496 tmpTable.removeAttr('width');
5497
5498 // If there is no width attribute or style, then allow the table to
5499 // collapse
5500 if ( tmpTable.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) {
5501 tmpTable.outerWidth( tableContainer.clientWidth );
5502 }
5503 }
5504 else if ( scrollY ) {
5505 tmpTable.outerWidth( tableContainer.clientWidth );
5506 }
5507 else if ( tableWidthAttr ) {
5508 tmpTable.outerWidth( tableWidthAttr );
5509 }
5510
5511 // Get the width of each column in the constructed table
5512 var total = 0;
5513 var bodyCells = tmpTable.find('tbody tr').eq(0).children();
5514
5515 for ( i=0 ; i<visibleColumns.length ; i++ ) {
5516 // Use getBounding for sub-pixel accuracy, which we then want to round up!
5517 var bounding = bodyCells[i].getBoundingClientRect().width;
5518
5519 // Total is tracked to remove any sub-pixel errors as the outerWidth
5520 // of the table might not equal the total given here
5521 total += bounding;
5522
5523 // Width for each column to use
5524 columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding );
5525 }
5526
5527 table.style.width = _fnStringToCss( total );
5528
5529 // Finished with the table - ditch it
5530 holder.remove();
5531
5532 // If there is a width attr, we want to attach an event listener which
5533 // allows the table sizing to automatically adjust when the window is
5534 // resized. Use the width attr rather than CSS, since we can't know if the
5535 // CSS is a relative value or absolute - DOM read is always px.
5536 if ( tableWidthAttr ) {
5537 table.style.width = _fnStringToCss( tableWidthAttr );
5538 }
5539
5540 if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) {
5541 var resize = DataTable.util.throttle( function () {
5542 var newWidth = _fnWrapperWidth(settings);
5543
5544 // Don't do it if destroying or the container width is 0
5545 if (! settings.bDestroying && newWidth !== 0) {
5546 _fnAdjustColumnSizing( settings );
5547 }
5548 } );
5549
5550 // For browsers that support it (~2020 onwards for wide support) we can watch for the
5551 // container changing width.
5552 if (window.ResizeObserver) {
5553 // This is a tricky beast - if the element is visible when `.observe()` is called,
5554 // then the callback is immediately run. Which we don't want. If the element isn't
5555 // visible, then it isn't run, but we want it to run when it is then made visible.
5556 // This flag allows the above to be satisfied.
5557 var first = $(settings.nTableWrapper).is(':visible');
5558
5559 // Use an empty div to attach the observer so it isn't impacted by height changes
5560 var resizer = $('<div>')
5561 .css({
5562 width: '100%',
5563 height: 0
5564 })
5565 .addClass('dt-autosize')
5566 .appendTo(settings.nTableWrapper);
5567
5568 settings.resizeObserver = new ResizeObserver(function (e) {
5569 if (first) {
5570 first = false;
5571 }
5572 else {
5573 resize();
5574 }
5575 });
5576
5577 settings.resizeObserver.observe(resizer[0]);
5578 }
5579 else {
5580 // For old browsers, the best we can do is listen for a window resize
5581 $(window).on('resize.DT-'+settings.sInstance, resize);
5582 }
5583
5584 settings._reszEvt = true;
5585 }
5586 }
5587
5588 /**
5589 * Get the width of the DataTables wrapper element
5590 *
5591 * @param {*} settings DataTables settings object
5592 * @returns Width
5593 */
5594 function _fnWrapperWidth(settings) {
5595 return $(settings.nTableWrapper).is(':visible')
5596 ? $(settings.nTableWrapper).width()
5597 : 0;
5598 }
5599
5600 /**
5601 * Get the maximum strlen for each data column
5602 * @param {object} settings dataTables settings object
5603 * @param {int} colIdx column of interest
5604 * @returns {string} string of the max length
5605 * @memberof DataTable#oApi
5606 */
5607 function _fnGetMaxLenString( settings, colIdx )
5608 {
5609 var column = settings.aoColumns[colIdx];
5610
5611 if (! column.maxLenString) {
5612 var s, max='', maxLen = -1;
5613
5614 for ( var i=0, ien=settings.aiDisplayMaster.length ; i<ien ; i++ ) {
5615 var rowIdx = settings.aiDisplayMaster[i];
5616 var data = _fnGetRowDisplay(settings, rowIdx)[colIdx];
5617
5618 var cellString = data && typeof data === 'object' && data.nodeType
5619 ? data.innerHTML
5620 : data+'';
5621
5622 // Remove id / name attributes from elements so they
5623 // don't interfere with existing elements
5624 cellString = cellString
5625 .replace(/id=".*?"/g, '')
5626 .replace(/name=".*?"/g, '');
5627
5628 s = _stripHtml(cellString)
5629 .replace( /&nbsp;/g, ' ' );
5630
5631 if ( s.length > maxLen ) {
5632 // We want the HTML in the string, but the length that
5633 // is important is the stripped string
5634 max = cellString;
5635 maxLen = s.length;
5636 }
5637 }
5638
5639 column.maxLenString = max;
5640 }
5641
5642 return column.maxLenString;
5643 }
5644
5645
5646 /**
5647 * Append a CSS unit (only if required) to a string
5648 * @param {string} value to css-ify
5649 * @returns {string} value with css unit
5650 * @memberof DataTable#oApi
5651 */
5652 function _fnStringToCss( s )
5653 {
5654 if ( s === null ) {
5655 return '0px';
5656 }
5657
5658 if ( typeof s == 'number' ) {
5659 return s < 0 ?
5660 '0px' :
5661 s+'px';
5662 }
5663
5664 // Check it has a unit character already
5665 return s.match(/\d$/) ?
5666 s+'px' :
5667 s;
5668 }
5669
5670 /**
5671 * Re-insert the `col` elements for current visibility
5672 *
5673 * @param {*} settings DT settings
5674 */
5675 function _colGroup( settings ) {
5676 var cols = settings.aoColumns;
5677
5678 settings.colgroup.empty();
5679
5680 for (i=0 ; i<cols.length ; i++) {
5681 if (cols[i].bVisible) {
5682 settings.colgroup.append(cols[i].colEl);
5683 }
5684 }
5685 }
5686
5687
5688 function _fnSortInit( settings ) {
5689 var target = settings.nTHead;
5690 var headerRows = target.querySelectorAll('tr');
5691 var legacyTop = settings.bSortCellsTop;
5692 var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])';
5693
5694 // Legacy support for `orderCellsTop`
5695 if (legacyTop === true) {
5696 target = headerRows[0];
5697 }
5698 else if (legacyTop === false) {
5699 target = headerRows[ headerRows.length - 1 ];
5700 }
5701
5702 _fnSortAttachListener(
5703 settings,
5704 target,
5705 target === settings.nTHead
5706 ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector
5707 : 'th'+notSelector+', td'+notSelector
5708 );
5709
5710 // Need to resolve the user input array into our internal structure
5711 var order = [];
5712 _fnSortResolve( settings, order, settings.aaSorting );
5713
5714 settings.aaSorting = order;
5715 }
5716
5717
5718 function _fnSortAttachListener(settings, node, selector, column, callback) {
5719 _fnBindAction( node, selector, function (e) {
5720 var run = false;
5721 var columns = column === undefined
5722 ? _fnColumnsFromHeader( e.target )
5723 : [column];
5724
5725 if ( columns.length ) {
5726 for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
5727 var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey );
5728
5729 if (ret !== false) {
5730 run = true;
5731 }
5732
5733 // If the first entry is no sort, then subsequent
5734 // sort columns are ignored
5735 if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') {
5736 break;
5737 }
5738 }
5739
5740 if (run) {
5741 _fnProcessingRun(settings, true, function () {
5742 _fnSort( settings );
5743 _fnSortDisplay( settings, settings.aiDisplay );
5744
5745 _fnReDraw( settings, false, false );
5746
5747 if (callback) {
5748 callback();
5749 }
5750 });
5751 }
5752 }
5753 } );
5754 }
5755
5756 /**
5757 * Sort the display array to match the master's order
5758 * @param {*} settings
5759 */
5760 function _fnSortDisplay(settings, display) {
5761 if (display.length < 2) {
5762 return;
5763 }
5764
5765 var master = settings.aiDisplayMaster;
5766 var masterMap = {};
5767 var map = {};
5768 var i;
5769
5770 // Rather than needing an `indexOf` on master array, we can create a map
5771 for (i=0 ; i<master.length ; i++) {
5772 masterMap[master[i]] = i;
5773 }
5774
5775 // And then cache what would be the indexOf fom the display
5776 for (i=0 ; i<display.length ; i++) {
5777 map[display[i]] = masterMap[display[i]];
5778 }
5779
5780 display.sort(function(a, b){
5781 // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);`
5782 return map[a] - map[b];
5783 });
5784 }
5785
5786
5787 function _fnSortResolve (settings, nestedSort, sort) {
5788 var push = function ( a ) {
5789 if ($.isPlainObject(a)) {
5790 if (a.idx !== undefined) {
5791 // Index based ordering
5792 nestedSort.push([a.idx, a.dir]);
5793 }
5794 else if (a.name) {
5795 // Name based ordering
5796 var cols = _pluck( settings.aoColumns, 'sName');
5797 var idx = cols.indexOf(a.name);
5798
5799 if (idx !== -1) {
5800 nestedSort.push([idx, a.dir]);
5801 }
5802 }
5803 }
5804 else {
5805 // Plain column index and direction pair
5806 nestedSort.push(a);
5807 }
5808 };
5809
5810 if ( $.isPlainObject(sort) ) {
5811 // Object
5812 push(sort);
5813 }
5814 else if ( sort.length && typeof sort[0] === 'number' ) {
5815 // 1D array
5816 push(sort);
5817 }
5818 else if ( sort.length ) {
5819 // 2D array
5820 for (var z=0; z<sort.length; z++) {
5821 push(sort[z]); // Object or array
5822 }
5823 }
5824 }
5825
5826
5827 function _fnSortFlatten ( settings )
5828 {
5829 var
5830 i, k, kLen,
5831 aSort = [],
5832 extSort = DataTable.ext.type.order,
5833 aoColumns = settings.aoColumns,
5834 aDataSort, iCol, sType, srcCol,
5835 fixed = settings.aaSortingFixed,
5836 fixedObj = $.isPlainObject( fixed ),
5837 nestedSort = [];
5838
5839 if ( ! settings.oFeatures.bSort ) {
5840 return aSort;
5841 }
5842
5843 // Build the sort array, with pre-fix and post-fix options if they have been
5844 // specified
5845 if ( Array.isArray( fixed ) ) {
5846 _fnSortResolve( settings, nestedSort, fixed );
5847 }
5848
5849 if ( fixedObj && fixed.pre ) {
5850 _fnSortResolve( settings, nestedSort, fixed.pre );
5851 }
5852
5853 _fnSortResolve( settings, nestedSort, settings.aaSorting );
5854
5855 if (fixedObj && fixed.post ) {
5856 _fnSortResolve( settings, nestedSort, fixed.post );
5857 }
5858
5859 for ( i=0 ; i<nestedSort.length ; i++ )
5860 {
5861 srcCol = nestedSort[i][0];
5862
5863 if ( aoColumns[ srcCol ] ) {
5864 aDataSort = aoColumns[ srcCol ].aDataSort;
5865
5866 for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
5867 {
5868 iCol = aDataSort[k];
5869 sType = aoColumns[ iCol ].sType || 'string';
5870
5871 if ( nestedSort[i]._idx === undefined ) {
5872 nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]);
5873 }
5874
5875 if ( nestedSort[i][1] ) {
5876 aSort.push( {
5877 src: srcCol,
5878 col: iCol,
5879 dir: nestedSort[i][1],
5880 index: nestedSort[i]._idx,
5881 type: sType,
5882 formatter: extSort[ sType+"-pre" ],
5883 sorter: extSort[ sType+"-"+nestedSort[i][1] ]
5884 } );
5885 }
5886 }
5887 }
5888 }
5889
5890 return aSort;
5891 }
5892
5893 /**
5894 * Change the order of the table
5895 * @param {object} oSettings dataTables settings object
5896 * @memberof DataTable#oApi
5897 */
5898 function _fnSort ( oSettings, col, dir )
5899 {
5900 var
5901 i, ien, iLen,
5902 aiOrig = [],
5903 extSort = DataTable.ext.type.order,
5904 aoData = oSettings.aoData,
5905 sortCol,
5906 displayMaster = oSettings.aiDisplayMaster,
5907 aSort;
5908
5909 // Make sure the columns all have types defined
5910 _fnColumnTypes(oSettings);
5911
5912 // Allow a specific column to be sorted, which will _not_ alter the display
5913 // master
5914 if (col !== undefined) {
5915 var srcCol = oSettings.aoColumns[col];
5916
5917 aSort = [{
5918 src: col,
5919 col: col,
5920 dir: dir,
5921 index: 0,
5922 type: srcCol.sType,
5923 formatter: extSort[ srcCol.sType+"-pre" ],
5924 sorter: extSort[ srcCol.sType+"-"+dir ]
5925 }];
5926 displayMaster = displayMaster.slice();
5927 }
5928 else {
5929 aSort = _fnSortFlatten( oSettings );
5930 }
5931
5932 for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
5933 sortCol = aSort[i];
5934
5935 // Load the data needed for the sort, for each cell
5936 _fnSortData( oSettings, sortCol.col );
5937 }
5938
5939 /* No sorting required if server-side or no sorting array */
5940 if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
5941 {
5942 // Reset the initial positions on each pass so we get a stable sort
5943 for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
5944 aiOrig[ i ] = i;
5945 }
5946
5947 // If the first sort is desc, then reverse the array to preserve original
5948 // order, just in reverse
5949 if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) {
5950 aiOrig.reverse();
5951 }
5952
5953 /* Do the sort - here we want multi-column sorting based on a given data source (column)
5954 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
5955 * follow on it's own, but this is what we want (example two column sorting):
5956 * fnLocalSorting = function(a,b){
5957 * var test;
5958 * test = oSort['string-asc']('data11', 'data12');
5959 * if (test !== 0)
5960 * return test;
5961 * test = oSort['numeric-desc']('data21', 'data22');
5962 * if (test !== 0)
5963 * return test;
5964 * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
5965 * }
5966 * Basically we have a test for each sorting column, if the data in that column is equal,
5967 * test the next column. If all columns match, then we use a numeric sort on the row
5968 * positions in the original data array to provide a stable sort.
5969 */
5970 displayMaster.sort( function ( a, b ) {
5971 var
5972 x, y, k, test, sort,
5973 len=aSort.length,
5974 dataA = aoData[a]._aSortData,
5975 dataB = aoData[b]._aSortData;
5976
5977 for ( k=0 ; k<len ; k++ ) {
5978 sort = aSort[k];
5979
5980 // Data, which may have already been through a `-pre` function
5981 x = dataA[ sort.col ];
5982 y = dataB[ sort.col ];
5983
5984 if (sort.sorter) {
5985 // If there is a custom sorter (`-asc` or `-desc`) for this
5986 // data type, use it
5987 test = sort.sorter(x, y);
5988
5989 if ( test !== 0 ) {
5990 return test;
5991 }
5992 }
5993 else {
5994 // Otherwise, use generic sorting
5995 test = x<y ? -1 : x>y ? 1 : 0;
5996
5997 if ( test !== 0 ) {
5998 return sort.dir === 'asc' ? test : -test;
5999 }
6000 }
6001 }
6002
6003 x = aiOrig[a];
6004 y = aiOrig[b];
6005
6006 return x<y ? -1 : x>y ? 1 : 0;
6007 } );
6008 }
6009 else if ( aSort.length === 0 ) {
6010 // Apply index order
6011 displayMaster.sort(function (x, y) {
6012 return x<y ? -1 : x>y ? 1 : 0;
6013 });
6014 }
6015
6016 if (col === undefined) {
6017 // Tell the draw function that we have sorted the data
6018 oSettings.bSorted = true;
6019 oSettings.sortDetails = aSort;
6020
6021 _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] );
6022 }
6023
6024 return displayMaster;
6025 }
6026
6027
6028 /**
6029 * Function to run on user sort request
6030 * @param {object} settings dataTables settings object
6031 * @param {node} attachTo node to attach the handler to
6032 * @param {int} colIdx column sorting index
6033 * @param {int} addIndex Counter
6034 * @param {boolean} [shift=false] Shift click add
6035 * @param {function} [callback] callback function
6036 * @memberof DataTable#oApi
6037 */
6038 function _fnSortAdd ( settings, colIdx, addIndex, shift )
6039 {
6040 var col = settings.aoColumns[ colIdx ];
6041 var sorting = settings.aaSorting;
6042 var asSorting = col.asSorting;
6043 var nextSortIdx;
6044 var next = function ( a, overflow ) {
6045 var idx = a._idx;
6046 if ( idx === undefined ) {
6047 idx = asSorting.indexOf(a[1]);
6048 }
6049
6050 return idx+1 < asSorting.length ?
6051 idx+1 :
6052 overflow ?
6053 null :
6054 0;
6055 };
6056
6057 if ( ! col.bSortable ) {
6058 return false;
6059 }
6060
6061 // Convert to 2D array if needed
6062 if ( typeof sorting[0] === 'number' ) {
6063 sorting = settings.aaSorting = [ sorting ];
6064 }
6065
6066 // If appending the sort then we are multi-column sorting
6067 if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) {
6068 // Are we already doing some kind of sort on this column?
6069 var sortIdx = _pluck(sorting, '0').indexOf(colIdx);
6070
6071 if ( sortIdx !== -1 ) {
6072 // Yes, modify the sort
6073 nextSortIdx = next( sorting[sortIdx], true );
6074
6075 if ( nextSortIdx === null && sorting.length === 1 ) {
6076 nextSortIdx = 0; // can't remove sorting completely
6077 }
6078
6079 if ( nextSortIdx === null ) {
6080 sorting.splice( sortIdx, 1 );
6081 }
6082 else {
6083 sorting[sortIdx][1] = asSorting[ nextSortIdx ];
6084 sorting[sortIdx]._idx = nextSortIdx;
6085 }
6086 }
6087 else if (shift) {
6088 // No sort on this column yet, being added by shift click
6089 // add it as itself
6090 sorting.push( [ colIdx, asSorting[0], 0 ] );
6091 sorting[sorting.length-1]._idx = 0;
6092 }
6093 else {
6094 // No sort on this column yet, being added from a colspan
6095 // so add with same direction as first column
6096 sorting.push( [ colIdx, sorting[0][1], 0 ] );
6097 sorting[sorting.length-1]._idx = 0;
6098 }
6099 }
6100 else if ( sorting.length && sorting[0][0] == colIdx ) {
6101 // Single column - already sorting on this column, modify the sort
6102 nextSortIdx = next( sorting[0] );
6103
6104 sorting.length = 1;
6105 sorting[0][1] = asSorting[ nextSortIdx ];
6106 sorting[0]._idx = nextSortIdx;
6107 }
6108 else {
6109 // Single column - sort only on this column
6110 sorting.length = 0;
6111 sorting.push( [ colIdx, asSorting[0] ] );
6112 sorting[0]._idx = 0;
6113 }
6114 }
6115
6116
6117 /**
6118 * Set the sorting classes on table's body, Note: it is safe to call this function
6119 * when bSort and bSortClasses are false
6120 * @param {object} oSettings dataTables settings object
6121 * @memberof DataTable#oApi
6122 */
6123 function _fnSortingClasses( settings )
6124 {
6125 var oldSort = settings.aLastSort;
6126 var sortClass = settings.oClasses.order.position;
6127 var sort = _fnSortFlatten( settings );
6128 var features = settings.oFeatures;
6129 var i, ien, colIdx;
6130
6131 if ( features.bSort && features.bSortClasses ) {
6132 // Remove old sorting classes
6133 for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
6134 colIdx = oldSort[i].src;
6135
6136 // Remove column sorting
6137 $( _pluck( settings.aoData, 'anCells', colIdx ) )
6138 .removeClass( sortClass + (i<2 ? i+1 : 3) );
6139 }
6140
6141 // Add new column sorting
6142 for ( i=0, ien=sort.length ; i<ien ; i++ ) {
6143 colIdx = sort[i].src;
6144
6145 $( _pluck( settings.aoData, 'anCells', colIdx ) )
6146 .addClass( sortClass + (i<2 ? i+1 : 3) );
6147 }
6148 }
6149
6150 settings.aLastSort = sort;
6151 }
6152
6153
6154 // Get the data to sort a column, be it from cache, fresh (populating the
6155 // cache), or from a sort formatter
6156 function _fnSortData( settings, colIdx )
6157 {
6158 // Custom sorting function - provided by the sort data type
6159 var column = settings.aoColumns[ colIdx ];
6160 var customSort = DataTable.ext.order[ column.sSortDataType ];
6161 var customData;
6162
6163 if ( customSort ) {
6164 customData = customSort.call( settings.oInstance, settings, colIdx,
6165 _fnColumnIndexToVisible( settings, colIdx )
6166 );
6167 }
6168
6169 // Use / populate cache
6170 var row, cellData;
6171 var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
6172 var data = settings.aoData;
6173
6174 for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
6175 // Sparse array
6176 if (! data[rowIdx]) {
6177 continue;
6178 }
6179
6180 row = data[rowIdx];
6181
6182 if ( ! row._aSortData ) {
6183 row._aSortData = [];
6184 }
6185
6186 if ( ! row._aSortData[colIdx] || customSort ) {
6187 cellData = customSort ?
6188 customData[rowIdx] : // If there was a custom sort function, use data from there
6189 _fnGetCellData( settings, rowIdx, colIdx, 'sort' );
6190
6191 row._aSortData[ colIdx ] = formatter ?
6192 formatter( cellData, settings ) :
6193 cellData;
6194 }
6195 }
6196 }
6197
6198
6199 /**
6200 * State information for a table
6201 *
6202 * @param {*} settings
6203 * @returns State object
6204 */
6205 function _fnSaveState ( settings )
6206 {
6207 if (settings._bLoadingState) {
6208 return;
6209 }
6210
6211 // Sort state saving uses [[idx, order]] structure.
6212 var sorting = [];
6213 _fnSortResolve(settings, sorting, settings.aaSorting );
6214
6215 /* Store the interesting variables */
6216 var columns = settings.aoColumns;
6217 var state = {
6218 time: +new Date(),
6219 start: settings._iDisplayStart,
6220 length: settings._iDisplayLength,
6221 order: sorting.map(function (sort) {
6222 // If a column name is available, use it
6223 return columns[sort[0]] && columns[sort[0]].sName
6224 ? [ columns[sort[0]].sName, sort[1] ]
6225 : sort.slice();
6226 } ),
6227 search: $.extend({}, settings.oPreviousSearch),
6228 columns: settings.aoColumns.map( function ( col, i ) {
6229 return {
6230 name: col.sName,
6231 visible: col.bVisible,
6232 search: $.extend({}, settings.aoPreSearchCols[i])
6233 };
6234 } )
6235 };
6236
6237 settings.oSavedState = state;
6238 _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
6239
6240 if ( settings.oFeatures.bStateSave && !settings.bDestroying )
6241 {
6242 settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
6243 }
6244 }
6245
6246
6247 /**
6248 * Attempt to load a saved table state
6249 * @param {object} oSettings dataTables settings object
6250 * @param {object} oInit DataTables init object so we can override settings
6251 * @param {function} callback Callback to execute when the state has been loaded
6252 * @memberof DataTable#oApi
6253 */
6254 function _fnLoadState ( settings, init, callback )
6255 {
6256 if ( ! settings.oFeatures.bStateSave ) {
6257 callback();
6258 return;
6259 }
6260
6261 var loaded = function(state) {
6262 _fnImplementState(settings, state, callback);
6263 }
6264
6265 var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded );
6266
6267 if ( state !== undefined ) {
6268 _fnImplementState( settings, state, callback );
6269 }
6270 // otherwise, wait for the loaded callback to be executed
6271
6272 return true;
6273 }
6274
6275 function _fnImplementState ( settings, s, callback) {
6276 var i, ien;
6277 var columns = settings.aoColumns;
6278 var currentNames = _pluck(settings.aoColumns, 'sName');
6279
6280 settings._bLoadingState = true;
6281
6282 // When StateRestore was introduced the state could now be implemented at any time
6283 // Not just initialisation. To do this an api instance is required in some places
6284 var api = settings._bInitComplete ? new DataTable.Api(settings) : null;
6285
6286 if ( ! s || ! s.time ) {
6287 settings._bLoadingState = false;
6288 callback();
6289 return;
6290 }
6291
6292 // Reject old data
6293 var duration = settings.iStateDuration;
6294 if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
6295 settings._bLoadingState = false;
6296 callback();
6297 return;
6298 }
6299
6300 // Allow custom and plug-in manipulation functions to alter the saved data set and
6301 // cancelling of loading by returning false
6302 var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] );
6303 if ( abStateLoad.indexOf(false) !== -1 ) {
6304 settings._bLoadingState = false;
6305 callback();
6306 return;
6307 }
6308
6309 // Store the saved state so it might be accessed at any time
6310 settings.oLoadedState = $.extend( true, {}, s );
6311
6312 // This is needed for ColReorder, which has to happen first to allow all
6313 // the stored indexes to be usable. It is not publicly documented.
6314 _fnCallbackFire( settings, null, 'stateLoadInit', [settings, s], true );
6315
6316 // Page Length
6317 if ( s.length !== undefined ) {
6318 // If already initialised just set the value directly so that the select element is also updated
6319 if (api) {
6320 api.page.len(s.length)
6321 }
6322 else {
6323 settings._iDisplayLength = s.length;
6324 }
6325 }
6326
6327 // Restore key features
6328 if ( s.start !== undefined ) {
6329 if(api === null) {
6330 settings._iDisplayStart = s.start;
6331 settings.iInitDisplayStart = s.start;
6332 }
6333 else {
6334 _fnPageChange(settings, s.start/settings._iDisplayLength);
6335 }
6336 }
6337
6338 // Order
6339 if ( s.order !== undefined ) {
6340 settings.aaSorting = [];
6341 $.each( s.order, function ( i, col ) {
6342 var set = [ col[0], col[1] ];
6343
6344 // A column name was stored and should be used for restore
6345 if (typeof col[0] === 'string') {
6346 var idx = currentNames.indexOf(col[0]);
6347
6348 // Find the name from the current list of column names, or fallback to index 0
6349 set[0] = idx >= 0
6350 ? idx
6351 : 0;
6352 }
6353 else if (set[0] >= columns.length) {
6354 // If a column name, but it is out of bounds, set to 0
6355 set[0] = 0;
6356 }
6357
6358 settings.aaSorting.push(set);
6359 } );
6360 }
6361
6362 // Search
6363 if ( s.search !== undefined ) {
6364 $.extend( settings.oPreviousSearch, s.search );
6365 }
6366
6367 // Columns
6368 if ( s.columns ) {
6369 var set = s.columns;
6370 var incoming = _pluck(s.columns, 'name');
6371
6372 // Check if it is a 2.2 style state object with a `name` property for the columns, and if
6373 // the name was defined. If so, then create a new array that will map the state object
6374 // given, to the current columns (don't bother if they are already matching tho).
6375 if (incoming.join('').length && incoming.join('') !== currentNames.join('')) {
6376 set = [];
6377
6378 // For each column, try to find the name in the incoming array
6379 for (i=0 ; i<currentNames.length ; i++) {
6380 if (currentNames[i] != '') {
6381 var idx = incoming.indexOf(currentNames[i]);
6382
6383 if (idx >= 0) {
6384 set.push(s.columns[idx]);
6385 }
6386 else {
6387 // No matching column name in the state's columns, so this might be a new
6388 // column and thus can't have a state already.
6389 set.push({});
6390 }
6391 }
6392 else {
6393 // If no name, but other columns did have a name, then there is no knowing
6394 // where this one came from originally so it can't be restored.
6395 set.push({});
6396 }
6397 }
6398 }
6399
6400 // If the number of columns to restore is different from current, then all bets are off.
6401 if (set.length === columns.length) {
6402 for ( i=0, ien=set.length ; i<ien ; i++ ) {
6403 var col = set[i];
6404
6405 // Visibility
6406 if ( col.visible !== undefined ) {
6407 // If the api is defined, the table has been initialised so we need to use it rather than internal settings
6408 if (api) {
6409 // Don't redraw the columns on every iteration of this loop, we will do this at the end instead
6410 api.column(i).visible(col.visible, false);
6411 }
6412 else {
6413 columns[i].bVisible = col.visible;
6414 }
6415 }
6416
6417 // Search
6418 if ( col.search !== undefined ) {
6419 $.extend( settings.aoPreSearchCols[i], col.search );
6420 }
6421 }
6422
6423 // If the api is defined then we need to adjust the columns once the visibility has been changed
6424 if (api) {
6425 api.columns.adjust();
6426 }
6427 }
6428 }
6429
6430 settings._bLoadingState = false;
6431 _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] );
6432 callback();
6433 }
6434
6435 /**
6436 * Log an error message
6437 * @param {object} settings dataTables settings object
6438 * @param {int} level log error messages, or display them to the user
6439 * @param {string} msg error message
6440 * @param {int} tn Technical note id to get more information about the error.
6441 * @memberof DataTable#oApi
6442 */
6443 function _fnLog( settings, level, msg, tn )
6444 {
6445 msg = 'DataTables warning: '+
6446 (settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
6447
6448 if ( tn ) {
6449 msg += '. For more information about this error, please see '+
6450 'https://datatables.net/tn/'+tn;
6451 }
6452
6453 if ( ! level ) {
6454 // Backwards compatibility pre 1.10
6455 var ext = DataTable.ext;
6456 var type = ext.sErrMode || ext.errMode;
6457
6458 if ( settings ) {
6459 _fnCallbackFire( settings, null, 'dt-error', [ settings, tn, msg ], true );
6460 }
6461
6462 if ( type == 'alert' ) {
6463 alert( msg );
6464 }
6465 else if ( type == 'throw' ) {
6466 throw new Error(msg);
6467 }
6468 else if ( typeof type == 'function' ) {
6469 type( settings, tn, msg );
6470 }
6471 }
6472 else if ( window.console && console.log ) {
6473 console.log( msg );
6474 }
6475 }
6476
6477
6478 /**
6479 * See if a property is defined on one object, if so assign it to the other object
6480 * @param {object} ret target object
6481 * @param {object} src source object
6482 * @param {string} name property
6483 * @param {string} [mappedName] name to map too - optional, name used if not given
6484 * @memberof DataTable#oApi
6485 */
6486 function _fnMap( ret, src, name, mappedName )
6487 {
6488 if ( Array.isArray( name ) ) {
6489 $.each( name, function (i, val) {
6490 if ( Array.isArray( val ) ) {
6491 _fnMap( ret, src, val[0], val[1] );
6492 }
6493 else {
6494 _fnMap( ret, src, val );
6495 }
6496 } );
6497
6498 return;
6499 }
6500
6501 if ( mappedName === undefined ) {
6502 mappedName = name;
6503 }
6504
6505 if ( src[name] !== undefined ) {
6506 ret[mappedName] = src[name];
6507 }
6508 }
6509
6510
6511 /**
6512 * Extend objects - very similar to jQuery.extend, but deep copy objects, and
6513 * shallow copy arrays. The reason we need to do this, is that we don't want to
6514 * deep copy array init values (such as aaSorting) since the dev wouldn't be
6515 * able to override them, but we do want to deep copy arrays.
6516 * @param {object} out Object to extend
6517 * @param {object} extender Object from which the properties will be applied to
6518 * out
6519 * @param {boolean} breakRefs If true, then arrays will be sliced to take an
6520 * independent copy with the exception of the `data` or `aaData` parameters
6521 * if they are present. This is so you can pass in a collection to
6522 * DataTables and have that used as your data source without breaking the
6523 * references
6524 * @returns {object} out Reference, just for convenience - out === the return.
6525 * @memberof DataTable#oApi
6526 * @todo This doesn't take account of arrays inside the deep copied objects.
6527 */
6528 function _fnExtend( out, extender, breakRefs )
6529 {
6530 var val;
6531
6532 for ( var prop in extender ) {
6533 if ( Object.prototype.hasOwnProperty.call(extender, prop) ) {
6534 val = extender[prop];
6535
6536 if ( $.isPlainObject( val ) ) {
6537 if ( ! $.isPlainObject( out[prop] ) ) {
6538 out[prop] = {};
6539 }
6540 $.extend( true, out[prop], val );
6541 }
6542 else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) {
6543 out[prop] = val.slice();
6544 }
6545 else {
6546 out[prop] = val;
6547 }
6548 }
6549 }
6550
6551 return out;
6552 }
6553
6554
6555 /**
6556 * Bind an event handers to allow a click or return key to activate the callback.
6557 * This is good for accessibility since a return on the keyboard will have the
6558 * same effect as a click, if the element has focus.
6559 * @param {element} n Element to bind the action to
6560 * @param {object|string} selector Selector (for delegated events) or data object
6561 * to pass to the triggered function
6562 * @param {function} fn Callback function for when the event is triggered
6563 * @memberof DataTable#oApi
6564 */
6565 function _fnBindAction( n, selector, fn )
6566 {
6567 $(n)
6568 .on( 'click.DT', selector, function (e) {
6569 fn(e);
6570 } )
6571 .on( 'keypress.DT', selector, function (e){
6572 if ( e.which === 13 ) {
6573 e.preventDefault();
6574 fn(e);
6575 }
6576 } )
6577 .on( 'selectstart.DT', selector, function () {
6578 // Don't want a double click resulting in text selection
6579 return false;
6580 } );
6581 }
6582
6583
6584 /**
6585 * Register a callback function. Easily allows a callback function to be added to
6586 * an array store of callback functions that can then all be called together.
6587 * @param {object} settings dataTables settings object
6588 * @param {string} store Name of the array storage for the callbacks in oSettings
6589 * @param {function} fn Function to be called back
6590 * @memberof DataTable#oApi
6591 */
6592 function _fnCallbackReg( settings, store, fn )
6593 {
6594 if ( fn ) {
6595 settings[store].push(fn);
6596 }
6597 }
6598
6599
6600 /**
6601 * Fire callback functions and trigger events. Note that the loop over the
6602 * callback array store is done backwards! Further note that you do not want to
6603 * fire off triggers in time sensitive applications (for example cell creation)
6604 * as its slow.
6605 * @param {object} settings dataTables settings object
6606 * @param {string} callbackArr Name of the array storage for the callbacks in
6607 * oSettings
6608 * @param {string} eventName Name of the jQuery custom event to trigger. If
6609 * null no trigger is fired
6610 * @param {array} args Array of arguments to pass to the callback function /
6611 * trigger
6612 * @param {boolean} [bubbles] True if the event should bubble
6613 * @memberof DataTable#oApi
6614 */
6615 function _fnCallbackFire( settings, callbackArr, eventName, args, bubbles )
6616 {
6617 var ret = [];
6618
6619 if ( callbackArr ) {
6620 ret = settings[callbackArr].slice().reverse().map( function (val) {
6621 return val.apply( settings.oInstance, args );
6622 } );
6623 }
6624
6625 if ( eventName !== null) {
6626 var e = $.Event( eventName+'.dt' );
6627 var table = $(settings.nTable);
6628
6629 // Expose the DataTables API on the event object for easy access
6630 e.dt = settings.api;
6631
6632 table[bubbles ? 'trigger' : 'triggerHandler']( e, args );
6633
6634 // If not yet attached to the document, trigger the event
6635 // on the body directly to sort of simulate the bubble
6636 if (bubbles && table.parents('body').length === 0) {
6637 $('body').trigger( e, args );
6638 }
6639
6640 ret.push( e.result );
6641 }
6642
6643 return ret;
6644 }
6645
6646
6647 function _fnLengthOverflow ( settings )
6648 {
6649 var
6650 start = settings._iDisplayStart,
6651 end = settings.fnDisplayEnd(),
6652 len = settings._iDisplayLength;
6653
6654 /* If we have space to show extra rows (backing up from the end point - then do so */
6655 if ( start >= end )
6656 {
6657 start = end - len;
6658 }
6659
6660 // Keep the start record on the current page
6661 start -= (start % len);
6662
6663 if ( len === -1 || start < 0 )
6664 {
6665 start = 0;
6666 }
6667
6668 settings._iDisplayStart = start;
6669 }
6670
6671
6672 function _fnRenderer( settings, type )
6673 {
6674 var renderer = settings.renderer;
6675 var host = DataTable.ext.renderer[type];
6676
6677 if ( $.isPlainObject( renderer ) && renderer[type] ) {
6678 // Specific renderer for this type. If available use it, otherwise use
6679 // the default.
6680 return host[renderer[type]] || host._;
6681 }
6682 else if ( typeof renderer === 'string' ) {
6683 // Common renderer - if there is one available for this type use it,
6684 // otherwise use the default
6685 return host[renderer] || host._;
6686 }
6687
6688 // Use the default
6689 return host._;
6690 }
6691
6692
6693 /**
6694 * Detect the data source being used for the table. Used to simplify the code
6695 * a little (ajax) and to make it compress a little smaller.
6696 *
6697 * @param {object} settings dataTables settings object
6698 * @returns {string} Data source
6699 * @memberof DataTable#oApi
6700 */
6701 function _fnDataSource ( settings )
6702 {
6703 if ( settings.oFeatures.bServerSide ) {
6704 return 'ssp';
6705 }
6706 else if ( settings.ajax ) {
6707 return 'ajax';
6708 }
6709 return 'dom';
6710 }
6711
6712 /**
6713 * Common replacement for language strings
6714 *
6715 * @param {*} settings DT settings object
6716 * @param {*} str String with values to replace
6717 * @param {*} entries Plural number for _ENTRIES_ - can be undefined
6718 * @returns String
6719 */
6720 function _fnMacros ( settings, str, entries )
6721 {
6722 // When infinite scrolling, we are always starting at 1. _iDisplayStart is
6723 // used only internally
6724 var
6725 formatter = settings.fnFormatNumber,
6726 start = settings._iDisplayStart+1,
6727 len = settings._iDisplayLength,
6728 vis = settings.fnRecordsDisplay(),
6729 max = settings.fnRecordsTotal(),
6730 all = len === -1;
6731
6732 return str.
6733 replace(/_START_/g, formatter.call( settings, start ) ).
6734 replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ).
6735 replace(/_MAX_/g, formatter.call( settings, max ) ).
6736 replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
6737 replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
6738 replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ).
6739 replace(/_ENTRIES_/g, settings.api.i18n('entries', '', entries) ).
6740 replace(/_ENTRIES-MAX_/g, settings.api.i18n('entries', '', max) ).
6741 replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) );
6742 }
6743
6744 /**
6745 * Add elements to an array as quickly as possible, but stack stafe.
6746 *
6747 * @param {*} arr Array to add the data to
6748 * @param {*} data Data array that is to be added
6749 * @returns
6750 */
6751 function _fnArrayApply(arr, data) {
6752 if (! data) {
6753 return;
6754 }
6755
6756 // Chrome can throw a max stack error if apply is called with
6757 // too large an array, but apply is faster.
6758 if (data.length < 10000) {
6759 arr.push.apply(arr, data);
6760 }
6761 else {
6762 for (i=0 ; i<data.length ; i++) {
6763 arr.push(data[i]);
6764 }
6765 }
6766 }
6767
6768
6769
6770 /**
6771 * Computed structure of the DataTables API, defined by the options passed to
6772 * `DataTable.Api.register()` when building the API.
6773 *
6774 * The structure is built in order to speed creation and extension of the Api
6775 * objects since the extensions are effectively pre-parsed.
6776 *
6777 * The array is an array of objects with the following structure, where this
6778 * base array represents the Api prototype base:
6779 *
6780 * [
6781 * {
6782 * name: 'data' -- string - Property name
6783 * val: function () {}, -- function - Api method (or undefined if just an object
6784 * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result
6785 * propExt: [ ... ] -- array - Array of Api object definitions to extend the property
6786 * },
6787 * {
6788 * name: 'row'
6789 * val: {},
6790 * methodExt: [ ... ],
6791 * propExt: [
6792 * {
6793 * name: 'data'
6794 * val: function () {},
6795 * methodExt: [ ... ],
6796 * propExt: [ ... ]
6797 * },
6798 * ...
6799 * ]
6800 * }
6801 * ]
6802 *
6803 * @type {Array}
6804 * @ignore
6805 */
6806 var __apiStruct = [];
6807
6808
6809 /**
6810 * `Array.prototype` reference.
6811 *
6812 * @type object
6813 * @ignore
6814 */
6815 var __arrayProto = Array.prototype;
6816
6817
6818 /**
6819 * Abstraction for `context` parameter of the `Api` constructor to allow it to
6820 * take several different forms for ease of use.
6821 *
6822 * Each of the input parameter types will be converted to a DataTables settings
6823 * object where possible.
6824 *
6825 * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one
6826 * of:
6827 *
6828 * * `string` - jQuery selector. Any DataTables' matching the given selector
6829 * with be found and used.
6830 * * `node` - `TABLE` node which has already been formed into a DataTable.
6831 * * `jQuery` - A jQuery object of `TABLE` nodes.
6832 * * `object` - DataTables settings object
6833 * * `DataTables.Api` - API instance
6834 * @return {array|null} Matching DataTables settings objects. `null` or
6835 * `undefined` is returned if no matching DataTable is found.
6836 * @ignore
6837 */
6838 var _toSettings = function ( mixed )
6839 {
6840 var idx, jq;
6841 var settings = DataTable.settings;
6842 var tables = _pluck(settings, 'nTable');
6843
6844 if ( ! mixed ) {
6845 return [];
6846 }
6847 else if ( mixed.nTable && mixed.oFeatures ) {
6848 // DataTables settings object
6849 return [ mixed ];
6850 }
6851 else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
6852 // Table node
6853 idx = tables.indexOf(mixed);
6854 return idx !== -1 ? [ settings[idx] ] : null;
6855 }
6856 else if ( mixed && typeof mixed.settings === 'function' ) {
6857 return mixed.settings().toArray();
6858 }
6859 else if ( typeof mixed === 'string' ) {
6860 // jQuery selector
6861 jq = $(mixed).get();
6862 }
6863 else if ( mixed instanceof $ ) {
6864 // jQuery object (also DataTables instance)
6865 jq = mixed.get();
6866 }
6867
6868 if ( jq ) {
6869 return settings.filter(function (v, idx) {
6870 return jq.includes(tables[idx]);
6871 });
6872 }
6873 };
6874
6875
6876 /**
6877 * DataTables API class - used to control and interface with one or more
6878 * DataTables enhanced tables.
6879 *
6880 * The API class is heavily based on jQuery, presenting a chainable interface
6881 * that you can use to interact with tables. Each instance of the API class has
6882 * a "context" - i.e. the tables that it will operate on. This could be a single
6883 * table, all tables on a page or a sub-set thereof.
6884 *
6885 * Additionally the API is designed to allow you to easily work with the data in
6886 * the tables, retrieving and manipulating it as required. This is done by
6887 * presenting the API class as an array like interface. The contents of the
6888 * array depend upon the actions requested by each method (for example
6889 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
6890 * return an array of objects or arrays depending upon your table's
6891 * configuration). The API object has a number of array like methods (`push`,
6892 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
6893 * `unique` etc) to assist your working with the data held in a table.
6894 *
6895 * Most methods (those which return an Api instance) are chainable, which means
6896 * the return from a method call also has all of the methods available that the
6897 * top level object had. For example, these two calls are equivalent:
6898 *
6899 * // Not chained
6900 * api.row.add( {...} );
6901 * api.draw();
6902 *
6903 * // Chained
6904 * api.row.add( {...} ).draw();
6905 *
6906 * @class DataTable.Api
6907 * @param {array|object|string|jQuery} context DataTable identifier. This is
6908 * used to define which DataTables enhanced tables this API will operate on.
6909 * Can be one of:
6910 *
6911 * * `string` - jQuery selector. Any DataTables' matching the given selector
6912 * with be found and used.
6913 * * `node` - `TABLE` node which has already been formed into a DataTable.
6914 * * `jQuery` - A jQuery object of `TABLE` nodes.
6915 * * `object` - DataTables settings object
6916 * @param {array} [data] Data to initialise the Api instance with.
6917 *
6918 * @example
6919 * // Direct initialisation during DataTables construction
6920 * var api = $('#example').DataTable();
6921 *
6922 * @example
6923 * // Initialisation using a DataTables jQuery object
6924 * var api = $('#example').dataTable().api();
6925 *
6926 * @example
6927 * // Initialisation as a constructor
6928 * var api = new DataTable.Api( 'table.dataTable' );
6929 */
6930 _Api = function ( context, data )
6931 {
6932 if ( ! (this instanceof _Api) ) {
6933 return new _Api( context, data );
6934 }
6935
6936 var i;
6937 var settings = [];
6938 var ctxSettings = function ( o ) {
6939 var a = _toSettings( o );
6940 if ( a ) {
6941 settings.push.apply( settings, a );
6942 }
6943 };
6944
6945 if ( Array.isArray( context ) ) {
6946 for ( i=0 ; i<context.length ; i++ ) {
6947 ctxSettings( context[i] );
6948 }
6949 }
6950 else {
6951 ctxSettings( context );
6952 }
6953
6954 // Remove duplicates
6955 this.context = settings.length > 1
6956 ? _unique( settings )
6957 : settings;
6958
6959 // Initial data
6960 _fnArrayApply(this, data);
6961
6962 // selector
6963 this.selector = {
6964 rows: null,
6965 cols: null,
6966 opts: null
6967 };
6968
6969 _Api.extend( this, this, __apiStruct );
6970 };
6971
6972 DataTable.Api = _Api;
6973
6974 // Don't destroy the existing prototype, just extend it. Required for jQuery 2's
6975 // isPlainObject.
6976 $.extend( _Api.prototype, {
6977 any: function ()
6978 {
6979 return this.count() !== 0;
6980 },
6981
6982 context: [], // array of table settings objects
6983
6984 count: function ()
6985 {
6986 return this.flatten().length;
6987 },
6988
6989 each: function ( fn )
6990 {
6991 for ( var i=0, ien=this.length ; i<ien; i++ ) {
6992 fn.call( this, this[i], i, this );
6993 }
6994
6995 return this;
6996 },
6997
6998 eq: function ( idx )
6999 {
7000 var ctx = this.context;
7001
7002 return ctx.length > idx ?
7003 new _Api( ctx[idx], this[idx] ) :
7004 null;
7005 },
7006
7007 filter: function ( fn )
7008 {
7009 var a = __arrayProto.filter.call( this, fn, this );
7010
7011 return new _Api( this.context, a );
7012 },
7013
7014 flatten: function ()
7015 {
7016 var a = [];
7017
7018 return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
7019 },
7020
7021 get: function ( idx )
7022 {
7023 return this[ idx ];
7024 },
7025
7026 join: __arrayProto.join,
7027
7028 includes: function ( find ) {
7029 return this.indexOf( find ) === -1 ? false : true;
7030 },
7031
7032 indexOf: __arrayProto.indexOf,
7033
7034 iterator: function ( flatten, type, fn, alwaysNew ) {
7035 var
7036 a = [], ret,
7037 i, ien, j, jen,
7038 context = this.context,
7039 rows, items, item,
7040 selector = this.selector;
7041
7042 // Argument shifting
7043 if ( typeof flatten === 'string' ) {
7044 alwaysNew = fn;
7045 fn = type;
7046 type = flatten;
7047 flatten = false;
7048 }
7049
7050 for ( i=0, ien=context.length ; i<ien ; i++ ) {
7051 var apiInst = new _Api( context[i] );
7052
7053 if ( type === 'table' ) {
7054 ret = fn.call( apiInst, context[i], i );
7055
7056 if ( ret !== undefined ) {
7057 a.push( ret );
7058 }
7059 }
7060 else if ( type === 'columns' || type === 'rows' ) {
7061 // this has same length as context - one entry for each table
7062 ret = fn.call( apiInst, context[i], this[i], i );
7063
7064 if ( ret !== undefined ) {
7065 a.push( ret );
7066 }
7067 }
7068 else if ( type === 'every' || type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
7069 // columns and rows share the same structure.
7070 // 'this' is an array of column indexes for each context
7071 items = this[i];
7072
7073 if ( type === 'column-rows' ) {
7074 rows = _selector_row_indexes( context[i], selector.opts );
7075 }
7076
7077 for ( j=0, jen=items.length ; j<jen ; j++ ) {
7078 item = items[j];
7079
7080 if ( type === 'cell' ) {
7081 ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
7082 }
7083 else {
7084 ret = fn.call( apiInst, context[i], item, i, j, rows );
7085 }
7086
7087 if ( ret !== undefined ) {
7088 a.push( ret );
7089 }
7090 }
7091 }
7092 }
7093
7094 if ( a.length || alwaysNew ) {
7095 var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
7096 var apiSelector = api.selector;
7097 apiSelector.rows = selector.rows;
7098 apiSelector.cols = selector.cols;
7099 apiSelector.opts = selector.opts;
7100 return api;
7101 }
7102 return this;
7103 },
7104
7105 lastIndexOf: __arrayProto.lastIndexOf,
7106
7107 length: 0,
7108
7109 map: function ( fn )
7110 {
7111 var a = __arrayProto.map.call( this, fn, this );
7112
7113 return new _Api( this.context, a );
7114 },
7115
7116 pluck: function ( prop )
7117 {
7118 var fn = DataTable.util.get(prop);
7119
7120 return this.map( function ( el ) {
7121 return fn(el);
7122 } );
7123 },
7124
7125 pop: __arrayProto.pop,
7126
7127 push: __arrayProto.push,
7128
7129 reduce: __arrayProto.reduce,
7130
7131 reduceRight: __arrayProto.reduceRight,
7132
7133 reverse: __arrayProto.reverse,
7134
7135 // Object with rows, columns and opts
7136 selector: null,
7137
7138 shift: __arrayProto.shift,
7139
7140 slice: function () {
7141 return new _Api( this.context, this );
7142 },
7143
7144 sort: __arrayProto.sort,
7145
7146 splice: __arrayProto.splice,
7147
7148 toArray: function ()
7149 {
7150 return __arrayProto.slice.call( this );
7151 },
7152
7153 to$: function ()
7154 {
7155 return $( this );
7156 },
7157
7158 toJQuery: function ()
7159 {
7160 return $( this );
7161 },
7162
7163 unique: function ()
7164 {
7165 return new _Api( this.context, _unique(this.toArray()) );
7166 },
7167
7168 unshift: __arrayProto.unshift
7169 } );
7170
7171
7172 function _api_scope( scope, fn, struc ) {
7173 return function () {
7174 var ret = fn.apply( scope || this, arguments );
7175
7176 // Method extension
7177 _Api.extend( ret, ret, struc.methodExt );
7178 return ret;
7179 };
7180 }
7181
7182 function _api_find( src, name ) {
7183 for ( var i=0, ien=src.length ; i<ien ; i++ ) {
7184 if ( src[i].name === name ) {
7185 return src[i];
7186 }
7187 }
7188 return null;
7189 }
7190
7191 window.__apiStruct = __apiStruct;
7192
7193 _Api.extend = function ( scope, obj, ext )
7194 {
7195 // Only extend API instances and static properties of the API
7196 if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
7197 return;
7198 }
7199
7200 var
7201 i, ien,
7202 struct;
7203
7204 for ( i=0, ien=ext.length ; i<ien ; i++ ) {
7205 struct = ext[i];
7206
7207 if (struct.name === '__proto__') {
7208 continue;
7209 }
7210
7211 // Value
7212 obj[ struct.name ] = struct.type === 'function' ?
7213 _api_scope( scope, struct.val, struct ) :
7214 struct.type === 'object' ?
7215 {} :
7216 struct.val;
7217
7218 obj[ struct.name ].__dt_wrapper = true;
7219
7220 // Property extension
7221 _Api.extend( scope, obj[ struct.name ], struct.propExt );
7222 }
7223 };
7224
7225 // [
7226 // {
7227 // name: 'data' -- string - Property name
7228 // val: function () {}, -- function - Api method (or undefined if just an object
7229 // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result
7230 // propExt: [ ... ] -- array - Array of Api object definitions to extend the property
7231 // },
7232 // {
7233 // name: 'row'
7234 // val: {},
7235 // methodExt: [ ... ],
7236 // propExt: [
7237 // {
7238 // name: 'data'
7239 // val: function () {},
7240 // methodExt: [ ... ],
7241 // propExt: [ ... ]
7242 // },
7243 // ...
7244 // ]
7245 // }
7246 // ]
7247
7248
7249 _Api.register = _api_register = function ( name, val )
7250 {
7251 if ( Array.isArray( name ) ) {
7252 for ( var j=0, jen=name.length ; j<jen ; j++ ) {
7253 _Api.register( name[j], val );
7254 }
7255 return;
7256 }
7257
7258 var
7259 i, ien,
7260 heir = name.split('.'),
7261 struct = __apiStruct,
7262 key, method;
7263
7264 for ( i=0, ien=heir.length ; i<ien ; i++ ) {
7265 method = heir[i].indexOf('()') !== -1;
7266 key = method ?
7267 heir[i].replace('()', '') :
7268 heir[i];
7269
7270 var src = _api_find( struct, key );
7271 if ( ! src ) {
7272 src = {
7273 name: key,
7274 val: {},
7275 methodExt: [],
7276 propExt: [],
7277 type: 'object'
7278 };
7279 struct.push( src );
7280 }
7281
7282 if ( i === ien-1 ) {
7283 src.val = val;
7284 src.type = typeof val === 'function' ?
7285 'function' :
7286 $.isPlainObject( val ) ?
7287 'object' :
7288 'other';
7289 }
7290 else {
7291 struct = method ?
7292 src.methodExt :
7293 src.propExt;
7294 }
7295 }
7296 };
7297
7298 _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
7299 _Api.register( pluralName, val );
7300
7301 _Api.register( singularName, function () {
7302 var ret = val.apply( this, arguments );
7303
7304 if ( ret === this ) {
7305 // Returned item is the API instance that was passed in, return it
7306 return this;
7307 }
7308 else if ( ret instanceof _Api ) {
7309 // New API instance returned, want the value from the first item
7310 // in the returned array for the singular result.
7311 return ret.length ?
7312 Array.isArray( ret[0] ) ?
7313 new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
7314 ret[0] :
7315 undefined;
7316 }
7317
7318 // Non-API return - just fire it back
7319 return ret;
7320 } );
7321 };
7322
7323
7324 /**
7325 * Selector for HTML tables. Apply the given selector to the give array of
7326 * DataTables settings objects.
7327 *
7328 * @param {string|integer} [selector] jQuery selector string or integer
7329 * @param {array} Array of DataTables settings objects to be filtered
7330 * @return {array}
7331 * @ignore
7332 */
7333 var __table_selector = function ( selector, a )
7334 {
7335 if ( Array.isArray(selector) ) {
7336 var result = [];
7337
7338 selector.forEach(function (sel) {
7339 var inner = __table_selector(sel, a);
7340
7341 _fnArrayApply(result, inner);
7342 });
7343
7344 return result.filter( function (item) {
7345 return item;
7346 });
7347 }
7348
7349 // Integer is used to pick out a table by index
7350 if ( typeof selector === 'number' ) {
7351 return [ a[ selector ] ];
7352 }
7353
7354 // Perform a jQuery selector on the table nodes
7355 var nodes = a.map( function (el) {
7356 return el.nTable;
7357 } );
7358
7359 return $(nodes)
7360 .filter( selector )
7361 .map( function () {
7362 // Need to translate back from the table node to the settings
7363 var idx = nodes.indexOf(this);
7364 return a[ idx ];
7365 } )
7366 .toArray();
7367 };
7368
7369
7370
7371 /**
7372 * Context selector for the API's context (i.e. the tables the API instance
7373 * refers to.
7374 *
7375 * @name DataTable.Api#tables
7376 * @param {string|integer} [selector] Selector to pick which tables the iterator
7377 * should operate on. If not given, all tables in the current context are
7378 * used. This can be given as a jQuery selector (for example `':gt(0)'`) to
7379 * select multiple tables or as an integer to select a single table.
7380 * @returns {DataTable.Api} Returns a new API instance if a selector is given.
7381 */
7382 _api_register( 'tables()', function ( selector ) {
7383 // A new instance is created if there was a selector specified
7384 return selector !== undefined && selector !== null ?
7385 new _Api( __table_selector( selector, this.context ) ) :
7386 this;
7387 } );
7388
7389
7390 _api_register( 'table()', function ( selector ) {
7391 var tables = this.tables( selector );
7392 var ctx = tables.context;
7393
7394 // Truncate to the first matched table
7395 return ctx.length ?
7396 new _Api( ctx[0] ) :
7397 tables;
7398 } );
7399
7400 // Common methods, combined to reduce size
7401 [
7402 ['nodes', 'node', 'nTable'],
7403 ['body', 'body', 'nTBody'],
7404 ['header', 'header', 'nTHead'],
7405 ['footer', 'footer', 'nTFoot'],
7406 ].forEach(function (item) {
7407 _api_registerPlural(
7408 'tables().' + item[0] + '()',
7409 'table().' + item[1] + '()' ,
7410 function () {
7411 return this.iterator( 'table', function ( ctx ) {
7412 return ctx[item[2]];
7413 }, 1 );
7414 }
7415 );
7416 });
7417
7418 // Structure methods
7419 [
7420 ['header', 'aoHeader'],
7421 ['footer', 'aoFooter'],
7422 ].forEach(function (item) {
7423 _api_register( 'table().' + item[0] + '.structure()' , function (selector) {
7424 var indexes = this.columns(selector).indexes().flatten();
7425 var ctx = this.context[0];
7426
7427 return _fnHeaderLayout(ctx, ctx[item[1]], indexes);
7428 } );
7429 })
7430
7431
7432 _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
7433 return this.iterator( 'table', function ( ctx ) {
7434 return ctx.nTableWrapper;
7435 }, 1 );
7436 } );
7437
7438 _api_register( 'tables().every()', function ( fn ) {
7439 var that = this;
7440
7441 return this.iterator('table', function (s, i) {
7442 fn.call(that.table(i), i);
7443 });
7444 });
7445
7446 _api_register( 'caption()', function ( value, side ) {
7447 var context = this.context;
7448
7449 // Getter - return existing node's content
7450 if ( value === undefined ) {
7451 var caption = context[0].captionNode;
7452
7453 return caption && context.length ?
7454 caption.innerHTML :
7455 null;
7456 }
7457
7458 return this.iterator( 'table', function ( ctx ) {
7459 var table = $(ctx.nTable);
7460 var caption = $(ctx.captionNode);
7461 var container = $(ctx.nTableWrapper);
7462
7463 // Create the node if it doesn't exist yet
7464 if ( ! caption.length ) {
7465 caption = $('<caption/>').html( value );
7466 ctx.captionNode = caption[0];
7467
7468 // If side isn't set, we need to insert into the document to let the
7469 // CSS decide so we can read it back, otherwise there is no way to
7470 // know if the CSS would put it top or bottom for scrolling
7471 if (! side) {
7472 table.prepend(caption);
7473
7474 side = caption.css('caption-side');
7475 }
7476 }
7477
7478 caption.html( value );
7479
7480 if ( side ) {
7481 caption.css( 'caption-side', side );
7482 caption[0]._captionSide = side;
7483 }
7484
7485 if (container.find('div.dataTables_scroll').length) {
7486 var selector = (side === 'top' ? 'Head' : 'Foot');
7487
7488 container.find('div.dataTables_scroll'+ selector +' table').prepend(caption);
7489 }
7490 else {
7491 table.prepend(caption);
7492 }
7493 }, 1 );
7494 } );
7495
7496 _api_register( 'caption.node()', function () {
7497 var ctx = this.context;
7498
7499 return ctx.length ? ctx[0].captionNode : null;
7500 } );
7501
7502
7503 /**
7504 * Redraw the tables in the current context.
7505 */
7506 _api_register( 'draw()', function ( paging ) {
7507 return this.iterator( 'table', function ( settings ) {
7508 if ( paging === 'page' ) {
7509 _fnDraw( settings );
7510 }
7511 else {
7512 if ( typeof paging === 'string' ) {
7513 paging = paging === 'full-hold' ?
7514 false :
7515 true;
7516 }
7517
7518 _fnReDraw( settings, paging===false );
7519 }
7520 } );
7521 } );
7522
7523
7524
7525 /**
7526 * Get the current page index.
7527 *
7528 * @return {integer} Current page index (zero based)
7529 *//**
7530 * Set the current page.
7531 *
7532 * Note that if you attempt to show a page which does not exist, DataTables will
7533 * not throw an error, but rather reset the paging.
7534 *
7535 * @param {integer|string} action The paging action to take. This can be one of:
7536 * * `integer` - The page index to jump to
7537 * * `string` - An action to take:
7538 * * `first` - Jump to first page.
7539 * * `next` - Jump to the next page
7540 * * `previous` - Jump to previous page
7541 * * `last` - Jump to the last page.
7542 * @returns {DataTables.Api} this
7543 */
7544 _api_register( 'page()', function ( action ) {
7545 if ( action === undefined ) {
7546 return this.page.info().page; // not an expensive call
7547 }
7548
7549 // else, have an action to take on all tables
7550 return this.iterator( 'table', function ( settings ) {
7551 _fnPageChange( settings, action );
7552 } );
7553 } );
7554
7555
7556 /**
7557 * Paging information for the first table in the current context.
7558 *
7559 * If you require paging information for another table, use the `table()` method
7560 * with a suitable selector.
7561 *
7562 * @return {object} Object with the following properties set:
7563 * * `page` - Current page index (zero based - i.e. the first page is `0`)
7564 * * `pages` - Total number of pages
7565 * * `start` - Display index for the first record shown on the current page
7566 * * `end` - Display index for the last record shown on the current page
7567 * * `length` - Display length (number of records). Note that generally `start
7568 * + length = end`, but this is not always true, for example if there are
7569 * only 2 records to show on the final page, with a length of 10.
7570 * * `recordsTotal` - Full data set length
7571 * * `recordsDisplay` - Data set length once the current filtering criterion
7572 * are applied.
7573 */
7574 _api_register( 'page.info()', function () {
7575 if ( this.context.length === 0 ) {
7576 return undefined;
7577 }
7578
7579 var
7580 settings = this.context[0],
7581 start = settings._iDisplayStart,
7582 len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
7583 visRecords = settings.fnRecordsDisplay(),
7584 all = len === -1;
7585
7586 return {
7587 "page": all ? 0 : Math.floor( start / len ),
7588 "pages": all ? 1 : Math.ceil( visRecords / len ),
7589 "start": start,
7590 "end": settings.fnDisplayEnd(),
7591 "length": len,
7592 "recordsTotal": settings.fnRecordsTotal(),
7593 "recordsDisplay": visRecords,
7594 "serverSide": _fnDataSource( settings ) === 'ssp'
7595 };
7596 } );
7597
7598
7599 /**
7600 * Get the current page length.
7601 *
7602 * @return {integer} Current page length. Note `-1` indicates that all records
7603 * are to be shown.
7604 *//**
7605 * Set the current page length.
7606 *
7607 * @param {integer} Page length to set. Use `-1` to show all records.
7608 * @returns {DataTables.Api} this
7609 */
7610 _api_register( 'page.len()', function ( len ) {
7611 // Note that we can't call this function 'length()' because `length`
7612 // is a Javascript property of functions which defines how many arguments
7613 // the function expects.
7614 if ( len === undefined ) {
7615 return this.context.length !== 0 ?
7616 this.context[0]._iDisplayLength :
7617 undefined;
7618 }
7619
7620 // else, set the page length
7621 return this.iterator( 'table', function ( settings ) {
7622 _fnLengthChange( settings, len );
7623 } );
7624 } );
7625
7626
7627
7628 var __reload = function ( settings, holdPosition, callback ) {
7629 // Use the draw event to trigger a callback
7630 if ( callback ) {
7631 var api = new _Api( settings );
7632
7633 api.one( 'draw', function () {
7634 callback( api.ajax.json() );
7635 } );
7636 }
7637
7638 if ( _fnDataSource( settings ) == 'ssp' ) {
7639 _fnReDraw( settings, holdPosition );
7640 }
7641 else {
7642 _fnProcessingDisplay( settings, true );
7643
7644 // Cancel an existing request
7645 var xhr = settings.jqXHR;
7646 if ( xhr && xhr.readyState !== 4 ) {
7647 xhr.abort();
7648 }
7649
7650 // Trigger xhr
7651 _fnBuildAjax( settings, {}, function( json ) {
7652 _fnClearTable( settings );
7653
7654 var data = _fnAjaxDataSrc( settings, json );
7655 for ( var i=0, ien=data.length ; i<ien ; i++ ) {
7656 _fnAddData( settings, data[i] );
7657 }
7658
7659 _fnReDraw( settings, holdPosition );
7660 _fnInitComplete( settings );
7661 _fnProcessingDisplay( settings, false );
7662 } );
7663 }
7664 };
7665
7666
7667 /**
7668 * Get the JSON response from the last Ajax request that DataTables made to the
7669 * server. Note that this returns the JSON from the first table in the current
7670 * context.
7671 *
7672 * @return {object} JSON received from the server.
7673 */
7674 _api_register( 'ajax.json()', function () {
7675 var ctx = this.context;
7676
7677 if ( ctx.length > 0 ) {
7678 return ctx[0].json;
7679 }
7680
7681 // else return undefined;
7682 } );
7683
7684
7685 /**
7686 * Get the data submitted in the last Ajax request
7687 */
7688 _api_register( 'ajax.params()', function () {
7689 var ctx = this.context;
7690
7691 if ( ctx.length > 0 ) {
7692 return ctx[0].oAjaxData;
7693 }
7694
7695 // else return undefined;
7696 } );
7697
7698
7699 /**
7700 * Reload tables from the Ajax data source. Note that this function will
7701 * automatically re-draw the table when the remote data has been loaded.
7702 *
7703 * @param {boolean} [reset=true] Reset (default) or hold the current paging
7704 * position. A full re-sort and re-filter is performed when this method is
7705 * called, which is why the pagination reset is the default action.
7706 * @returns {DataTables.Api} this
7707 */
7708 _api_register( 'ajax.reload()', function ( callback, resetPaging ) {
7709 return this.iterator( 'table', function (settings) {
7710 __reload( settings, resetPaging===false, callback );
7711 } );
7712 } );
7713
7714
7715 /**
7716 * Get the current Ajax URL. Note that this returns the URL from the first
7717 * table in the current context.
7718 *
7719 * @return {string} Current Ajax source URL
7720 *//**
7721 * Set the Ajax URL. Note that this will set the URL for all tables in the
7722 * current context.
7723 *
7724 * @param {string} url URL to set.
7725 * @returns {DataTables.Api} this
7726 */
7727 _api_register( 'ajax.url()', function ( url ) {
7728 var ctx = this.context;
7729
7730 if ( url === undefined ) {
7731 // get
7732 if ( ctx.length === 0 ) {
7733 return undefined;
7734 }
7735 ctx = ctx[0];
7736
7737 return $.isPlainObject( ctx.ajax ) ?
7738 ctx.ajax.url :
7739 ctx.ajax;
7740 }
7741
7742 // set
7743 return this.iterator( 'table', function ( settings ) {
7744 if ( $.isPlainObject( settings.ajax ) ) {
7745 settings.ajax.url = url;
7746 }
7747 else {
7748 settings.ajax = url;
7749 }
7750 } );
7751 } );
7752
7753
7754 /**
7755 * Load data from the newly set Ajax URL. Note that this method is only
7756 * available when `ajax.url()` is used to set a URL. Additionally, this method
7757 * has the same effect as calling `ajax.reload()` but is provided for
7758 * convenience when setting a new URL. Like `ajax.reload()` it will
7759 * automatically redraw the table once the remote data has been loaded.
7760 *
7761 * @returns {DataTables.Api} this
7762 */
7763 _api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
7764 // Same as a reload, but makes sense to present it for easy access after a
7765 // url change
7766 return this.iterator( 'table', function ( ctx ) {
7767 __reload( ctx, resetPaging===false, callback );
7768 } );
7769 } );
7770
7771
7772
7773
7774 var _selector_run = function ( type, selector, selectFn, settings, opts )
7775 {
7776 var
7777 out = [], res,
7778 a, i, ien, j, jen,
7779 selectorType = typeof selector;
7780
7781 // Can't just check for isArray here, as an API or jQuery instance might be
7782 // given with their array like look
7783 if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
7784 selector = [ selector ];
7785 }
7786
7787 for ( i=0, ien=selector.length ; i<ien ; i++ ) {
7788 // Only split on simple strings - complex expressions will be jQuery selectors
7789 a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ?
7790 selector[i].split(',') :
7791 [ selector[i] ];
7792
7793 for ( j=0, jen=a.length ; j<jen ; j++ ) {
7794 res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] );
7795
7796 // Remove empty items
7797 res = res.filter( function (item) {
7798 return item !== null && item !== undefined;
7799 });
7800
7801 if ( res && res.length ) {
7802 out = out.concat( res );
7803 }
7804 }
7805 }
7806
7807 // selector extensions
7808 var ext = _ext.selector[ type ];
7809 if ( ext.length ) {
7810 for ( i=0, ien=ext.length ; i<ien ; i++ ) {
7811 out = ext[i]( settings, opts, out );
7812 }
7813 }
7814
7815 return _unique( out );
7816 };
7817
7818
7819 var _selector_opts = function ( opts )
7820 {
7821 if ( ! opts ) {
7822 opts = {};
7823 }
7824
7825 // Backwards compatibility for 1.9- which used the terminology filter rather
7826 // than search
7827 if ( opts.filter && opts.search === undefined ) {
7828 opts.search = opts.filter;
7829 }
7830
7831 return $.extend( {
7832 search: 'none',
7833 order: 'current',
7834 page: 'all'
7835 }, opts );
7836 };
7837
7838
7839 // Reduce the API instance to the first item found
7840 var _selector_first = function ( old )
7841 {
7842 var inst = new _Api(old.context[0]);
7843
7844 // Use a push rather than passing to the constructor, since it will
7845 // merge arrays down automatically, which isn't what is wanted here
7846 if (old.length) {
7847 inst.push( old[0] );
7848 }
7849
7850 inst.selector = old.selector;
7851
7852 // Limit to a single row / column / cell
7853 if (inst.length && inst[0].length > 1) {
7854 inst[0].splice(1);
7855 }
7856
7857 return inst;
7858 };
7859
7860
7861 var _selector_row_indexes = function ( settings, opts )
7862 {
7863 var
7864 i, ien, tmp, a=[],
7865 displayFiltered = settings.aiDisplay,
7866 displayMaster = settings.aiDisplayMaster;
7867
7868 var
7869 search = opts.search, // none, applied, removed
7870 order = opts.order, // applied, current, index (original - compatibility with 1.9)
7871 page = opts.page; // all, current
7872
7873 if ( _fnDataSource( settings ) == 'ssp' ) {
7874 // In server-side processing mode, most options are irrelevant since
7875 // rows not shown don't exist and the index order is the applied order
7876 // Removed is a special case - for consistency just return an empty
7877 // array
7878 return search === 'removed' ?
7879 [] :
7880 _range( 0, displayMaster.length );
7881 }
7882
7883 if ( page == 'current' ) {
7884 // Current page implies that order=current and filter=applied, since it is
7885 // fairly senseless otherwise, regardless of what order and search actually
7886 // are
7887 for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
7888 a.push( displayFiltered[i] );
7889 }
7890 }
7891 else if ( order == 'current' || order == 'applied' ) {
7892 if ( search == 'none') {
7893 a = displayMaster.slice();
7894 }
7895 else if ( search == 'applied' ) {
7896 a = displayFiltered.slice();
7897 }
7898 else if ( search == 'removed' ) {
7899 // O(n+m) solution by creating a hash map
7900 var displayFilteredMap = {};
7901
7902 for ( i=0, ien=displayFiltered.length ; i<ien ; i++ ) {
7903 displayFilteredMap[displayFiltered[i]] = null;
7904 }
7905
7906 displayMaster.forEach(function (item) {
7907 if (! Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) {
7908 a.push(item);
7909 }
7910 });
7911 }
7912 }
7913 else if ( order == 'index' || order == 'original' ) {
7914 for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
7915 if (! settings.aoData[i]) {
7916 continue;
7917 }
7918
7919 if ( search == 'none' ) {
7920 a.push( i );
7921 }
7922 else { // applied | removed
7923 tmp = displayFiltered.indexOf(i);
7924
7925 if ((tmp === -1 && search == 'removed') ||
7926 (tmp >= 0 && search == 'applied') )
7927 {
7928 a.push( i );
7929 }
7930 }
7931 }
7932 }
7933 else if ( typeof order === 'number' ) {
7934 // Order the rows by the given column
7935 var ordered = _fnSort(settings, order, 'asc');
7936
7937 if (search === 'none') {
7938 a = ordered;
7939 }
7940 else { // applied | removed
7941 for (i=0; i<ordered.length; i++) {
7942 tmp = displayFiltered.indexOf(ordered[i]);
7943
7944 if ((tmp === -1 && search == 'removed') ||
7945 (tmp >= 0 && search == 'applied') )
7946 {
7947 a.push( ordered[i] );
7948 }
7949 }
7950 }
7951 }
7952
7953 return a;
7954 };
7955
7956
7957 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7958 * Rows
7959 *
7960 * {} - no selector - use all available rows
7961 * {integer} - row aoData index
7962 * {node} - TR node
7963 * {string} - jQuery selector to apply to the TR elements
7964 * {array} - jQuery array of nodes, or simply an array of TR nodes
7965 *
7966 */
7967 var __row_selector = function ( settings, selector, opts )
7968 {
7969 var rows;
7970 var run = function ( sel ) {
7971 var selInt = _intVal( sel );
7972 var aoData = settings.aoData;
7973
7974 // Short cut - selector is a number and no options provided (default is
7975 // all records, so no need to check if the index is in there, since it
7976 // must be - dev error if the index doesn't exist).
7977 if ( selInt !== null && ! opts ) {
7978 return [ selInt ];
7979 }
7980
7981 if ( ! rows ) {
7982 rows = _selector_row_indexes( settings, opts );
7983 }
7984
7985 if ( selInt !== null && rows.indexOf(selInt) !== -1 ) {
7986 // Selector - integer
7987 return [ selInt ];
7988 }
7989 else if ( sel === null || sel === undefined || sel === '' ) {
7990 // Selector - none
7991 return rows;
7992 }
7993
7994 // Selector - function
7995 if ( typeof sel === 'function' ) {
7996 return rows.map( function (idx) {
7997 var row = aoData[ idx ];
7998 return sel( idx, row._aData, row.nTr ) ? idx : null;
7999 } );
8000 }
8001
8002 // Selector - node
8003 if ( sel.nodeName ) {
8004 var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup
8005 var cellIdx = sel._DT_CellIndex;
8006
8007 if ( rowIdx !== undefined ) {
8008 // Make sure that the row is actually still present in the table
8009 return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ?
8010 [ rowIdx ] :
8011 [];
8012 }
8013 else if ( cellIdx ) {
8014 return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ?
8015 [ cellIdx.row ] :
8016 [];
8017 }
8018 else {
8019 var host = $(sel).closest('*[data-dt-row]');
8020 return host.length ?
8021 [ host.data('dt-row') ] :
8022 [];
8023 }
8024 }
8025
8026 // ID selector. Want to always be able to select rows by id, regardless
8027 // of if the tr element has been created or not, so can't rely upon
8028 // jQuery here - hence a custom implementation. This does not match
8029 // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
8030 // but to select it using a CSS selector engine (like Sizzle or
8031 // querySelect) it would need to need to be escaped for some characters.
8032 // DataTables simplifies this for row selectors since you can select
8033 // only a row. A # indicates an id any anything that follows is the id -
8034 // unescaped.
8035 if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
8036 // get row index from id
8037 var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
8038 if ( rowObj !== undefined ) {
8039 return [ rowObj.idx ];
8040 }
8041
8042 // need to fall through to jQuery in case there is DOM id that
8043 // matches
8044 }
8045
8046 // Get nodes in the order from the `rows` array with null values removed
8047 var nodes = _removeEmpty(
8048 _pluck_order( settings.aoData, rows, 'nTr' )
8049 );
8050
8051 // Selector - jQuery selector string, array of nodes or jQuery object/
8052 // As jQuery's .filter() allows jQuery objects to be passed in filter,
8053 // it also allows arrays, so this will cope with all three options
8054 return $(nodes)
8055 .filter( sel )
8056 .map( function () {
8057 return this._DT_RowIndex;
8058 } )
8059 .toArray();
8060 };
8061
8062 var matched = _selector_run( 'row', selector, run, settings, opts );
8063
8064 if (opts.order === 'current' || opts.order === 'applied') {
8065 _fnSortDisplay(settings, matched);
8066 }
8067
8068 return matched;
8069 };
8070
8071
8072 _api_register( 'rows()', function ( selector, opts ) {
8073 // argument shifting
8074 if ( selector === undefined ) {
8075 selector = '';
8076 }
8077 else if ( $.isPlainObject( selector ) ) {
8078 opts = selector;
8079 selector = '';
8080 }
8081
8082 opts = _selector_opts( opts );
8083
8084 var inst = this.iterator( 'table', function ( settings ) {
8085 return __row_selector( settings, selector, opts );
8086 }, 1 );
8087
8088 // Want argument shifting here and in __row_selector?
8089 inst.selector.rows = selector;
8090 inst.selector.opts = opts;
8091
8092 return inst;
8093 } );
8094
8095 _api_register( 'rows().nodes()', function () {
8096 return this.iterator( 'row', function ( settings, row ) {
8097 return settings.aoData[ row ].nTr || undefined;
8098 }, 1 );
8099 } );
8100
8101 _api_register( 'rows().data()', function () {
8102 return this.iterator( true, 'rows', function ( settings, rows ) {
8103 return _pluck_order( settings.aoData, rows, '_aData' );
8104 }, 1 );
8105 } );
8106
8107 _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
8108 return this.iterator( 'row', function ( settings, row ) {
8109 var r = settings.aoData[ row ];
8110 return type === 'search' ? r._aFilterData : r._aSortData;
8111 }, 1 );
8112 } );
8113
8114 _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
8115 return this.iterator( 'row', function ( settings, row ) {
8116 _fnInvalidate( settings, row, src );
8117 } );
8118 } );
8119
8120 _api_registerPlural( 'rows().indexes()', 'row().index()', function () {
8121 return this.iterator( 'row', function ( settings, row ) {
8122 return row;
8123 }, 1 );
8124 } );
8125
8126 _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
8127 var a = [];
8128 var context = this.context;
8129
8130 // `iterator` will drop undefined values, but in this case we want them
8131 for ( var i=0, ien=context.length ; i<ien ; i++ ) {
8132 for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
8133 var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
8134 a.push( (hash === true ? '#' : '' )+ id );
8135 }
8136 }
8137
8138 return new _Api( context, a );
8139 } );
8140
8141 _api_registerPlural( 'rows().remove()', 'row().remove()', function () {
8142 this.iterator( 'row', function ( settings, row ) {
8143 var data = settings.aoData;
8144 var rowData = data[ row ];
8145
8146 // Delete from the display arrays
8147 var idx = settings.aiDisplayMaster.indexOf(row);
8148 if (idx !== -1) {
8149 settings.aiDisplayMaster.splice(idx, 1);
8150 }
8151
8152 // For server-side processing tables - subtract the deleted row from the count
8153 if ( settings._iRecordsDisplay > 0 ) {
8154 settings._iRecordsDisplay--;
8155 }
8156
8157 // Check for an 'overflow' they case for displaying the table
8158 _fnLengthOverflow( settings );
8159
8160 // Remove the row's ID reference if there is one
8161 var id = settings.rowIdFn( rowData._aData );
8162 if ( id !== undefined ) {
8163 delete settings.aIds[ id ];
8164 }
8165
8166 data[row] = null;
8167 } );
8168
8169 return this;
8170 } );
8171
8172
8173 _api_register( 'rows.add()', function ( rows ) {
8174 var newRows = this.iterator( 'table', function ( settings ) {
8175 var row, i, ien;
8176 var out = [];
8177
8178 for ( i=0, ien=rows.length ; i<ien ; i++ ) {
8179 row = rows[i];
8180
8181 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
8182 out.push( _fnAddTr( settings, row )[0] );
8183 }
8184 else {
8185 out.push( _fnAddData( settings, row ) );
8186 }
8187 }
8188
8189 return out;
8190 }, 1 );
8191
8192 // Return an Api.rows() extended instance, so rows().nodes() etc can be used
8193 var modRows = this.rows( -1 );
8194 modRows.pop();
8195 _fnArrayApply(modRows, newRows);
8196
8197 return modRows;
8198 } );
8199
8200
8201
8202
8203
8204 /**
8205 *
8206 */
8207 _api_register( 'row()', function ( selector, opts ) {
8208 return _selector_first( this.rows( selector, opts ) );
8209 } );
8210
8211
8212 _api_register( 'row().data()', function ( data ) {
8213 var ctx = this.context;
8214
8215 if ( data === undefined ) {
8216 // Get
8217 return ctx.length && this.length && this[0].length ?
8218 ctx[0].aoData[ this[0] ]._aData :
8219 undefined;
8220 }
8221
8222 // Set
8223 var row = ctx[0].aoData[ this[0] ];
8224 row._aData = data;
8225
8226 // If the DOM has an id, and the data source is an array
8227 if ( Array.isArray( data ) && row.nTr && row.nTr.id ) {
8228 _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id );
8229 }
8230
8231 // Automatically invalidate
8232 _fnInvalidate( ctx[0], this[0], 'data' );
8233
8234 return this;
8235 } );
8236
8237
8238 _api_register( 'row().node()', function () {
8239 var ctx = this.context;
8240
8241 if (ctx.length && this.length && this[0].length) {
8242 var row = ctx[0].aoData[ this[0] ];
8243
8244 if (row && row.nTr) {
8245 return row.nTr;
8246 }
8247 }
8248
8249 return null;
8250 } );
8251
8252
8253 _api_register( 'row.add()', function ( row ) {
8254 // Allow a jQuery object to be passed in - only a single row is added from
8255 // it though - the first element in the set
8256 if ( row instanceof $ && row.length ) {
8257 row = row[0];
8258 }
8259
8260 var rows = this.iterator( 'table', function ( settings ) {
8261 if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
8262 return _fnAddTr( settings, row )[0];
8263 }
8264 return _fnAddData( settings, row );
8265 } );
8266
8267 // Return an Api.rows() extended instance, with the newly added row selected
8268 return this.row( rows[0] );
8269 } );
8270
8271
8272 $(document).on('plugin-init.dt', function (e, context) {
8273 var api = new _Api( context );
8274
8275 api.on( 'stateSaveParams.DT', function ( e, settings, d ) {
8276 // This could be more compact with the API, but it is a lot faster as a simple
8277 // internal loop
8278 var idFn = settings.rowIdFn;
8279 var rows = settings.aiDisplayMaster;
8280 var ids = [];
8281
8282 for (var i=0 ; i<rows.length ; i++) {
8283 var rowIdx = rows[i];
8284 var data = settings.aoData[rowIdx];
8285
8286 if (data._detailsShow) {
8287 ids.push( '#' + idFn(data._aData) );
8288 }
8289 }
8290
8291 d.childRows = ids;
8292 });
8293
8294 // For future state loads (e.g. with StateRestore)
8295 api.on( 'stateLoaded.DT', function (e, settings, state) {
8296 __details_state_load( api, state );
8297 });
8298
8299 // And the initial load state
8300 __details_state_load( api, api.state.loaded() );
8301 });
8302
8303 var __details_state_load = function (api, state)
8304 {
8305 if ( state && state.childRows ) {
8306 api
8307 .rows( state.childRows.map(function (id) {
8308 // Escape any `:` characters from the row id. Accounts for
8309 // already escaped characters.
8310 return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:");
8311 }) )
8312 .every( function () {
8313 _fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] )
8314 });
8315 }
8316 }
8317
8318 var __details_add = function ( ctx, row, data, klass )
8319 {
8320 // Convert to array of TR elements
8321 var rows = [];
8322 var addRow = function ( r, k ) {
8323 // Recursion to allow for arrays of jQuery objects
8324 if ( Array.isArray( r ) || r instanceof $ ) {
8325 for ( var i=0, ien=r.length ; i<ien ; i++ ) {
8326 addRow( r[i], k );
8327 }
8328 return;
8329 }
8330
8331 // If we get a TR element, then just add it directly - up to the dev
8332 // to add the correct number of columns etc
8333 if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
8334 r.setAttribute( 'data-dt-row', row.idx );
8335 rows.push( r );
8336 }
8337 else {
8338 // Otherwise create a row with a wrapper
8339 var created = $('<tr><td></td></tr>')
8340 .attr( 'data-dt-row', row.idx )
8341 .addClass( k );
8342
8343 $('td', created)
8344 .addClass( k )
8345 .html( r )[0].colSpan = _fnVisbleColumns( ctx );
8346
8347 rows.push( created[0] );
8348 }
8349 };
8350
8351 addRow( data, klass );
8352
8353 if ( row._details ) {
8354 row._details.detach();
8355 }
8356
8357 row._details = $(rows);
8358
8359 // If the children were already shown, that state should be retained
8360 if ( row._detailsShow ) {
8361 row._details.insertAfter( row.nTr );
8362 }
8363 };
8364
8365
8366 // Make state saving of child row details async to allow them to be batch processed
8367 var __details_state = DataTable.util.throttle(
8368 function (ctx) {
8369 _fnSaveState( ctx[0] )
8370 },
8371 500
8372 );
8373
8374
8375 var __details_remove = function ( api, idx )
8376 {
8377 var ctx = api.context;
8378
8379 if ( ctx.length ) {
8380 var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
8381
8382 if ( row && row._details ) {
8383 row._details.remove();
8384
8385 row._detailsShow = undefined;
8386 row._details = undefined;
8387 $( row.nTr ).removeClass( 'dt-hasChild' );
8388 __details_state( ctx );
8389 }
8390 }
8391 };
8392
8393
8394 var __details_display = function ( api, show ) {
8395 var ctx = api.context;
8396
8397 if ( ctx.length && api.length ) {
8398 var row = ctx[0].aoData[ api[0] ];
8399
8400 if ( row._details ) {
8401 row._detailsShow = show;
8402
8403 if ( show ) {
8404 row._details.insertAfter( row.nTr );
8405 $( row.nTr ).addClass( 'dt-hasChild' );
8406 }
8407 else {
8408 row._details.detach();
8409 $( row.nTr ).removeClass( 'dt-hasChild' );
8410 }
8411
8412 _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] )
8413
8414 __details_events( ctx[0] );
8415 __details_state( ctx );
8416 }
8417 }
8418 };
8419
8420
8421 var __details_events = function ( settings )
8422 {
8423 var api = new _Api( settings );
8424 var namespace = '.dt.DT_details';
8425 var drawEvent = 'draw'+namespace;
8426 var colvisEvent = 'column-sizing'+namespace;
8427 var destroyEvent = 'destroy'+namespace;
8428 var data = settings.aoData;
8429
8430 api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
8431
8432 if ( _pluck( data, '_details' ).length > 0 ) {
8433 // On each draw, insert the required elements into the document
8434 api.on( drawEvent, function ( e, ctx ) {
8435 if ( settings !== ctx ) {
8436 return;
8437 }
8438
8439 api.rows( {page:'current'} ).eq(0).each( function (idx) {
8440 // Internal data grab
8441 var row = data[ idx ];
8442
8443 if ( row._detailsShow ) {
8444 row._details.insertAfter( row.nTr );
8445 }
8446 } );
8447 } );
8448
8449 // Column visibility change - update the colspan
8450 api.on( colvisEvent, function ( e, ctx ) {
8451 if ( settings !== ctx ) {
8452 return;
8453 }
8454
8455 // Update the colspan for the details rows (note, only if it already has
8456 // a colspan)
8457 var row, visible = _fnVisbleColumns( ctx );
8458
8459 for ( var i=0, ien=data.length ; i<ien ; i++ ) {
8460 row = data[i];
8461
8462 if ( row && row._details ) {
8463 row._details.each(function () {
8464 var el = $(this).children('td');
8465
8466 if (el.length == 1) {
8467 el.attr('colspan', visible);
8468 }
8469 });
8470 }
8471 }
8472 } );
8473
8474 // Table destroyed - nuke any child rows
8475 api.on( destroyEvent, function ( e, ctx ) {
8476 if ( settings !== ctx ) {
8477 return;
8478 }
8479
8480 for ( var i=0, ien=data.length ; i<ien ; i++ ) {
8481 if ( data[i] && data[i]._details ) {
8482 __details_remove( api, i );
8483 }
8484 }
8485 } );
8486 }
8487 };
8488
8489 // Strings for the method names to help minification
8490 var _emp = '';
8491 var _child_obj = _emp+'row().child';
8492 var _child_mth = _child_obj+'()';
8493
8494 // data can be:
8495 // tr
8496 // string
8497 // jQuery or array of any of the above
8498 _api_register( _child_mth, function ( data, klass ) {
8499 var ctx = this.context;
8500
8501 if ( data === undefined ) {
8502 // get
8503 return ctx.length && this.length && ctx[0].aoData[ this[0] ]
8504 ? ctx[0].aoData[ this[0] ]._details
8505 : undefined;
8506 }
8507 else if ( data === true ) {
8508 // show
8509 this.child.show();
8510 }
8511 else if ( data === false ) {
8512 // remove
8513 __details_remove( this );
8514 }
8515 else if ( ctx.length && this.length ) {
8516 // set
8517 __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
8518 }
8519
8520 return this;
8521 } );
8522
8523
8524 _api_register( [
8525 _child_obj+'.show()',
8526 _child_mth+'.show()' // only when `child()` was called with parameters (without
8527 ], function () { // it returns an object and this method is not executed)
8528 __details_display( this, true );
8529 return this;
8530 } );
8531
8532
8533 _api_register( [
8534 _child_obj+'.hide()',
8535 _child_mth+'.hide()' // only when `child()` was called with parameters (without
8536 ], function () { // it returns an object and this method is not executed)
8537 __details_display( this, false );
8538 return this;
8539 } );
8540
8541
8542 _api_register( [
8543 _child_obj+'.remove()',
8544 _child_mth+'.remove()' // only when `child()` was called with parameters (without
8545 ], function () { // it returns an object and this method is not executed)
8546 __details_remove( this );
8547 return this;
8548 } );
8549
8550
8551 _api_register( _child_obj+'.isShown()', function () {
8552 var ctx = this.context;
8553
8554 if ( ctx.length && this.length && ctx[0].aoData[ this[0] ] ) {
8555 // _detailsShown as false or undefined will fall through to return false
8556 return ctx[0].aoData[ this[0] ]._detailsShow || false;
8557 }
8558 return false;
8559 } );
8560
8561
8562
8563 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
8564 * Columns
8565 *
8566 * {integer} - column index (>=0 count from left, <0 count from right)
8567 * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right)
8568 * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right)
8569 * "{string}:name" - column name
8570 * "{string}" - jQuery selector on column header nodes
8571 *
8572 */
8573
8574 // can be an array of these items, comma separated list, or an array of comma
8575 // separated lists
8576
8577 var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/;
8578
8579
8580 // r1 and r2 are redundant - but it means that the parameters match for the
8581 // iterator callback in columns().data()
8582 var __columnData = function ( settings, column, r1, r2, rows, type ) {
8583 var a = [];
8584 for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
8585 a.push( _fnGetCellData( settings, rows[row], column, type ) );
8586 }
8587 return a;
8588 };
8589
8590
8591 var __column_header = function ( settings, column, row ) {
8592 var header = settings.aoHeader;
8593 var target = row !== undefined
8594 ? row
8595 : settings.bSortCellsTop // legacy support
8596 ? 0
8597 : header.length - 1;
8598
8599 return header[target][column].cell;
8600 };
8601
8602 var __column_selector = function ( settings, selector, opts )
8603 {
8604 var
8605 columns = settings.aoColumns,
8606 names = _pluck( columns, 'sName' ),
8607 titles = _pluck( columns, 'sTitle' ),
8608 cells = DataTable.util.get('[].[].cell')(settings.aoHeader),
8609 nodes = _unique( _flatten([], cells) );
8610
8611 var run = function ( s ) {
8612 var selInt = _intVal( s );
8613
8614 // Selector - all
8615 if ( s === '' ) {
8616 return _range( columns.length );
8617 }
8618
8619 // Selector - index
8620 if ( selInt !== null ) {
8621 return [ selInt >= 0 ?
8622 selInt : // Count from left
8623 columns.length + selInt // Count from right (+ because its a negative value)
8624 ];
8625 }
8626
8627 // Selector = function
8628 if ( typeof s === 'function' ) {
8629 var rows = _selector_row_indexes( settings, opts );
8630
8631 return columns.map(function (col, idx) {
8632 return s(
8633 idx,
8634 __columnData( settings, idx, 0, 0, rows ),
8635 __column_header( settings, idx )
8636 ) ? idx : null;
8637 });
8638 }
8639
8640 // jQuery or string selector
8641 var match = typeof s === 'string' ?
8642 s.match( __re_column_selector ) :
8643 '';
8644
8645 if ( match ) {
8646 switch( match[2] ) {
8647 case 'visIdx':
8648 case 'visible':
8649 // Selector is a column index
8650 if (match[1] && match[1].match(/^\d+$/)) {
8651 var idx = parseInt( match[1], 10 );
8652
8653 // Visible index given, convert to column index
8654 if ( idx < 0 ) {
8655 // Counting from the right
8656 var visColumns = columns.map( function (col,i) {
8657 return col.bVisible ? i : null;
8658 } );
8659 return [ visColumns[ visColumns.length + idx ] ];
8660 }
8661 // Counting from the left
8662 return [ _fnVisibleToColumnIndex( settings, idx ) ];
8663 }
8664
8665 return columns.map( function (col, idx) {
8666 // Not visible, can't match
8667 if (! col.bVisible) {
8668 return null;
8669 }
8670
8671 // Selector
8672 if (match[1]) {
8673 return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null;
8674 }
8675
8676 // `:visible` on its own
8677 return idx;
8678 } );
8679
8680 case 'name':
8681 // match by name. `names` is column index complete and in order
8682 return names.map( function (name, i) {
8683 return name === match[1] ? i : null;
8684 } );
8685
8686 case 'title':
8687 // match by column title
8688 return titles.map( function (title, i) {
8689 return title === match[1] ? i : null;
8690 } );
8691
8692 default:
8693 return [];
8694 }
8695 }
8696
8697 // Cell in the table body
8698 if ( s.nodeName && s._DT_CellIndex ) {
8699 return [ s._DT_CellIndex.column ];
8700 }
8701
8702 // jQuery selector on the TH elements for the columns
8703 var jqResult = $( nodes )
8704 .filter( s )
8705 .map( function () {
8706 return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order
8707 } )
8708 .toArray()
8709 .sort(function (a, b) {
8710 return a - b;
8711 });
8712
8713 if ( jqResult.length || ! s.nodeName ) {
8714 return jqResult;
8715 }
8716
8717 // Otherwise a node which might have a `dt-column` data attribute, or be
8718 // a child or such an element
8719 var host = $(s).closest('*[data-dt-column]');
8720 return host.length ?
8721 [ host.data('dt-column') ] :
8722 [];
8723 };
8724
8725 return _selector_run( 'column', selector, run, settings, opts );
8726 };
8727
8728
8729 var __setColumnVis = function ( settings, column, vis ) {
8730 var
8731 cols = settings.aoColumns,
8732 col = cols[ column ],
8733 data = settings.aoData,
8734 cells, i, ien, tr;
8735
8736 // Get
8737 if ( vis === undefined ) {
8738 return col.bVisible;
8739 }
8740
8741 // Set
8742 // No change
8743 if ( col.bVisible === vis ) {
8744 return false;
8745 }
8746
8747 if ( vis ) {
8748 // Insert column
8749 // Need to decide if we should use appendChild or insertBefore
8750 var insertBefore = _pluck(cols, 'bVisible').indexOf(true, column+1);
8751
8752 for ( i=0, ien=data.length ; i<ien ; i++ ) {
8753 if (data[i]) {
8754 tr = data[i].nTr;
8755 cells = data[i].anCells;
8756
8757 if ( tr ) {
8758 // insertBefore can act like appendChild if 2nd arg is null
8759 tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
8760 }
8761 }
8762 }
8763 }
8764 else {
8765 // Remove column
8766 $( _pluck( settings.aoData, 'anCells', column ) ).detach();
8767 }
8768
8769 // Common actions
8770 col.bVisible = vis;
8771
8772 _colGroup(settings);
8773
8774 return true;
8775 };
8776
8777
8778 _api_register( 'columns()', function ( selector, opts ) {
8779 // argument shifting
8780 if ( selector === undefined ) {
8781 selector = '';
8782 }
8783 else if ( $.isPlainObject( selector ) ) {
8784 opts = selector;
8785 selector = '';
8786 }
8787
8788 opts = _selector_opts( opts );
8789
8790 var inst = this.iterator( 'table', function ( settings ) {
8791 return __column_selector( settings, selector, opts );
8792 }, 1 );
8793
8794 // Want argument shifting here and in _row_selector?
8795 inst.selector.cols = selector;
8796 inst.selector.opts = opts;
8797
8798 return inst;
8799 } );
8800
8801 _api_registerPlural( 'columns().header()', 'column().header()', function ( row ) {
8802 return this.iterator( 'column', function (settings, column) {
8803 return __column_header(settings, column, row);
8804 }, 1 );
8805 } );
8806
8807 _api_registerPlural( 'columns().footer()', 'column().footer()', function ( row ) {
8808 return this.iterator( 'column', function ( settings, column ) {
8809 var footer = settings.aoFooter;
8810
8811 if (! footer.length) {
8812 return null;
8813 }
8814
8815 return settings.aoFooter[row !== undefined ? row : 0][column].cell;
8816 }, 1 );
8817 } );
8818
8819 _api_registerPlural( 'columns().data()', 'column().data()', function () {
8820 return this.iterator( 'column-rows', __columnData, 1 );
8821 } );
8822
8823 _api_registerPlural( 'columns().render()', 'column().render()', function ( type ) {
8824 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
8825 return __columnData( settings, column, i, j, rows, type );
8826 }, 1 );
8827 } );
8828
8829 _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
8830 return this.iterator( 'column', function ( settings, column ) {
8831 return settings.aoColumns[column].mData;
8832 }, 1 );
8833 } );
8834
8835 _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
8836 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
8837 return _pluck_order( settings.aoData, rows,
8838 type === 'search' ? '_aFilterData' : '_aSortData', column
8839 );
8840 }, 1 );
8841 } );
8842
8843 _api_registerPlural( 'columns().init()', 'column().init()', function () {
8844 return this.iterator( 'column', function ( settings, column ) {
8845 return settings.aoColumns[column];
8846 }, 1 );
8847 } );
8848
8849 _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
8850 return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
8851 return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
8852 }, 1 );
8853 } );
8854
8855 _api_registerPlural( 'columns().titles()', 'column().title()', function (title, row) {
8856 return this.iterator( 'column', function ( settings, column ) {
8857 // Argument shifting
8858 if (typeof title === 'number') {
8859 row = title;
8860 title = undefined;
8861 }
8862
8863 var span = $('span.dt-column-title', this.column(column).header(row));
8864
8865 if (title !== undefined) {
8866 span.html(title);
8867 return this;
8868 }
8869
8870 return span.html();
8871 }, 1 );
8872 } );
8873
8874 _api_registerPlural( 'columns().types()', 'column().type()', function () {
8875 return this.iterator( 'column', function ( settings, column ) {
8876 var type = settings.aoColumns[column].sType;
8877
8878 // If the type was invalidated, then resolve it. This actually does
8879 // all columns at the moment. Would only happen once if getting all
8880 // column's data types.
8881 if (! type) {
8882 _fnColumnTypes(settings);
8883 }
8884
8885 return type;
8886 }, 1 );
8887 } );
8888
8889 _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
8890 var that = this;
8891 var changed = [];
8892 var ret = this.iterator( 'column', function ( settings, column ) {
8893 if ( vis === undefined ) {
8894 return settings.aoColumns[ column ].bVisible;
8895 } // else
8896
8897 if (__setColumnVis( settings, column, vis )) {
8898 changed.push(column);
8899 }
8900 } );
8901
8902 // Group the column visibility changes
8903 if ( vis !== undefined ) {
8904 this.iterator( 'table', function ( settings ) {
8905 // Redraw the header after changes
8906 _fnDrawHead( settings, settings.aoHeader );
8907 _fnDrawHead( settings, settings.aoFooter );
8908
8909 // Update colspan for no records display. Child rows and extensions will use their own
8910 // listeners to do this - only need to update the empty table item here
8911 if ( ! settings.aiDisplay.length ) {
8912 $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings));
8913 }
8914
8915 _fnSaveState( settings );
8916
8917 // Second loop once the first is done for events
8918 that.iterator( 'column', function ( settings, column ) {
8919 if (changed.includes(column)) {
8920 _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
8921 }
8922 } );
8923
8924 if ( changed.length && (calc === undefined || calc) ) {
8925 that.columns.adjust();
8926 }
8927 });
8928 }
8929
8930 return ret;
8931 } );
8932
8933 _api_registerPlural( 'columns().widths()', 'column().width()', function () {
8934 // Injects a fake row into the table for just a moment so the widths can
8935 // be read, regardless of colspan in the header and rows being present in
8936 // the body
8937 var columns = this.columns(':visible').count();
8938 var row = $('<tr>').html('<td>' + Array(columns).join('</td><td>') + '</td>');
8939
8940 $(this.table().body()).append(row);
8941
8942 var widths = row.children().map(function () {
8943 return $(this).outerWidth();
8944 });
8945
8946 row.remove();
8947
8948 return this.iterator( 'column', function ( settings, column ) {
8949 var visIdx = _fnColumnIndexToVisible( settings, column );
8950
8951 return visIdx !== null ? widths[visIdx] : 0;
8952 }, 1);
8953 } );
8954
8955 _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
8956 return this.iterator( 'column', function ( settings, column ) {
8957 return type === 'visible' ?
8958 _fnColumnIndexToVisible( settings, column ) :
8959 column;
8960 }, 1 );
8961 } );
8962
8963 _api_register( 'columns.adjust()', function () {
8964 return this.iterator( 'table', function ( settings ) {
8965 // Force a column sizing to happen with a manual call - otherwise it can skip
8966 // if the size hasn't changed
8967 settings.containerWidth = -1;
8968
8969 _fnAdjustColumnSizing( settings );
8970 }, 1 );
8971 } );
8972
8973 _api_register( 'column.index()', function ( type, idx ) {
8974 if ( this.context.length !== 0 ) {
8975 var ctx = this.context[0];
8976
8977 if ( type === 'fromVisible' || type === 'toData' ) {
8978 return _fnVisibleToColumnIndex( ctx, idx );
8979 }
8980 else if ( type === 'fromData' || type === 'toVisible' ) {
8981 return _fnColumnIndexToVisible( ctx, idx );
8982 }
8983 }
8984 } );
8985
8986 _api_register( 'column()', function ( selector, opts ) {
8987 return _selector_first( this.columns( selector, opts ) );
8988 } );
8989
8990 var __cell_selector = function ( settings, selector, opts )
8991 {
8992 var data = settings.aoData;
8993 var rows = _selector_row_indexes( settings, opts );
8994 var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
8995 var allCells = $(_flatten( [], cells ));
8996 var row;
8997 var columns = settings.aoColumns.length;
8998 var a, i, ien, j, o, host;
8999
9000 var run = function ( s ) {
9001 var fnSelector = typeof s === 'function';
9002
9003 if ( s === null || s === undefined || fnSelector ) {
9004 // All cells and function selectors
9005 a = [];
9006
9007 for ( i=0, ien=rows.length ; i<ien ; i++ ) {
9008 row = rows[i];
9009
9010 for ( j=0 ; j<columns ; j++ ) {
9011 o = {
9012 row: row,
9013 column: j
9014 };
9015
9016 if ( fnSelector ) {
9017 // Selector - function
9018 host = data[ row ];
9019
9020 if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
9021 a.push( o );
9022 }
9023 }
9024 else {
9025 // Selector - all
9026 a.push( o );
9027 }
9028 }
9029 }
9030
9031 return a;
9032 }
9033
9034 // Selector - index
9035 if ( $.isPlainObject( s ) ) {
9036 // Valid cell index and its in the array of selectable rows
9037 return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ?
9038 [s] :
9039 [];
9040 }
9041
9042 // Selector - jQuery filtered cells
9043 var jqResult = allCells
9044 .filter( s )
9045 .map( function (i, el) {
9046 return { // use a new object, in case someone changes the values
9047 row: el._DT_CellIndex.row,
9048 column: el._DT_CellIndex.column
9049 };
9050 } )
9051 .toArray();
9052
9053 if ( jqResult.length || ! s.nodeName ) {
9054 return jqResult;
9055 }
9056
9057 // Otherwise the selector is a node, and there is one last option - the
9058 // element might be a child of an element which has dt-row and dt-column
9059 // data attributes
9060 host = $(s).closest('*[data-dt-row]');
9061 return host.length ?
9062 [ {
9063 row: host.data('dt-row'),
9064 column: host.data('dt-column')
9065 } ] :
9066 [];
9067 };
9068
9069 return _selector_run( 'cell', selector, run, settings, opts );
9070 };
9071
9072
9073
9074
9075 _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
9076 // Argument shifting
9077 if ( $.isPlainObject( rowSelector ) ) {
9078 // Indexes
9079 if ( rowSelector.row === undefined ) {
9080 // Selector options in first parameter
9081 opts = rowSelector;
9082 rowSelector = null;
9083 }
9084 else {
9085 // Cell index objects in first parameter
9086 opts = columnSelector;
9087 columnSelector = null;
9088 }
9089 }
9090 if ( $.isPlainObject( columnSelector ) ) {
9091 opts = columnSelector;
9092 columnSelector = null;
9093 }
9094
9095 // Cell selector
9096 if ( columnSelector === null || columnSelector === undefined ) {
9097 return this.iterator( 'table', function ( settings ) {
9098 return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
9099 } );
9100 }
9101
9102 // The default built in options need to apply to row and columns
9103 var internalOpts = opts ? {
9104 page: opts.page,
9105 order: opts.order,
9106 search: opts.search
9107 } : {};
9108
9109 // Row + column selector
9110 var columns = this.columns( columnSelector, internalOpts );
9111 var rows = this.rows( rowSelector, internalOpts );
9112 var i, ien, j, jen;
9113
9114 var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) {
9115 var a = [];
9116
9117 for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
9118 for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
9119 a.push( {
9120 row: rows[idx][i],
9121 column: columns[idx][j]
9122 } );
9123 }
9124 }
9125
9126 return a;
9127 }, 1 );
9128
9129 // There is currently only one extension which uses a cell selector extension
9130 // It is a _major_ performance drag to run this if it isn't needed, so this is
9131 // an extension specific check at the moment
9132 var cells = opts && opts.selected ?
9133 this.cells( cellsNoOpts, opts ) :
9134 cellsNoOpts;
9135
9136 $.extend( cells.selector, {
9137 cols: columnSelector,
9138 rows: rowSelector,
9139 opts: opts
9140 } );
9141
9142 return cells;
9143 } );
9144
9145
9146 _api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
9147 return this.iterator( 'cell', function ( settings, row, column ) {
9148 var data = settings.aoData[ row ];
9149
9150 return data && data.anCells ?
9151 data.anCells[ column ] :
9152 undefined;
9153 }, 1 );
9154 } );
9155
9156
9157 _api_register( 'cells().data()', function () {
9158 return this.iterator( 'cell', function ( settings, row, column ) {
9159 return _fnGetCellData( settings, row, column );
9160 }, 1 );
9161 } );
9162
9163
9164 _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
9165 type = type === 'search' ? '_aFilterData' : '_aSortData';
9166
9167 return this.iterator( 'cell', function ( settings, row, column ) {
9168 return settings.aoData[ row ][ type ][ column ];
9169 }, 1 );
9170 } );
9171
9172
9173 _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
9174 return this.iterator( 'cell', function ( settings, row, column ) {
9175 return _fnGetCellData( settings, row, column, type );
9176 }, 1 );
9177 } );
9178
9179
9180 _api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
9181 return this.iterator( 'cell', function ( settings, row, column ) {
9182 return {
9183 row: row,
9184 column: column,
9185 columnVisible: _fnColumnIndexToVisible( settings, column )
9186 };
9187 }, 1 );
9188 } );
9189
9190
9191 _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
9192 return this.iterator( 'cell', function ( settings, row, column ) {
9193 _fnInvalidate( settings, row, src, column );
9194 } );
9195 } );
9196
9197
9198
9199 _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
9200 return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
9201 } );
9202
9203
9204 _api_register( 'cell().data()', function ( data ) {
9205 var ctx = this.context;
9206 var cell = this[0];
9207
9208 if ( data === undefined ) {
9209 // Get
9210 return ctx.length && cell.length ?
9211 _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
9212 undefined;
9213 }
9214
9215 // Set
9216 _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
9217 _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
9218
9219 return this;
9220 } );
9221
9222
9223
9224 /**
9225 * Get current ordering (sorting) that has been applied to the table.
9226 *
9227 * @returns {array} 2D array containing the sorting information for the first
9228 * table in the current context. Each element in the parent array represents
9229 * a column being sorted upon (i.e. multi-sorting with two columns would have
9230 * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
9231 * the column index that the sorting condition applies to, the second is the
9232 * direction of the sort (`desc` or `asc`) and, optionally, the third is the
9233 * index of the sorting order from the `column.sorting` initialisation array.
9234 *//**
9235 * Set the ordering for the table.
9236 *
9237 * @param {integer} order Column index to sort upon.
9238 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
9239 * @returns {DataTables.Api} this
9240 *//**
9241 * Set the ordering for the table.
9242 *
9243 * @param {array} order 1D array of sorting information to be applied.
9244 * @param {array} [...] Optional additional sorting conditions
9245 * @returns {DataTables.Api} this
9246 *//**
9247 * Set the ordering for the table.
9248 *
9249 * @param {array} order 2D array of sorting information to be applied.
9250 * @returns {DataTables.Api} this
9251 */
9252 _api_register( 'order()', function ( order, dir ) {
9253 var ctx = this.context;
9254 var args = Array.prototype.slice.call( arguments );
9255
9256 if ( order === undefined ) {
9257 // get
9258 return ctx.length !== 0 ?
9259 ctx[0].aaSorting :
9260 undefined;
9261 }
9262
9263 // set
9264 if ( typeof order === 'number' ) {
9265 // Simple column / direction passed in
9266 order = [ [ order, dir ] ];
9267 }
9268 else if ( args.length > 1 ) {
9269 // Arguments passed in (list of 1D arrays)
9270 order = args;
9271 }
9272 // otherwise a 2D array was passed in
9273
9274 return this.iterator( 'table', function ( settings ) {
9275 settings.aaSorting = Array.isArray(order) ? order.slice() : order;
9276 } );
9277 } );
9278
9279
9280 /**
9281 * Attach a sort listener to an element for a given column
9282 *
9283 * @param {node|jQuery|string} node Identifier for the element(s) to attach the
9284 * listener to. This can take the form of a single DOM node, a jQuery
9285 * collection of nodes or a jQuery selector which will identify the node(s).
9286 * @param {integer} column the column that a click on this node will sort on
9287 * @param {function} [callback] callback function when sort is run
9288 * @returns {DataTables.Api} this
9289 */
9290 _api_register( 'order.listener()', function ( node, column, callback ) {
9291 return this.iterator( 'table', function ( settings ) {
9292 _fnSortAttachListener(settings, node, {}, column, callback);
9293 } );
9294 } );
9295
9296
9297 _api_register( 'order.fixed()', function ( set ) {
9298 if ( ! set ) {
9299 var ctx = this.context;
9300 var fixed = ctx.length ?
9301 ctx[0].aaSortingFixed :
9302 undefined;
9303
9304 return Array.isArray( fixed ) ?
9305 { pre: fixed } :
9306 fixed;
9307 }
9308
9309 return this.iterator( 'table', function ( settings ) {
9310 settings.aaSortingFixed = $.extend( true, {}, set );
9311 } );
9312 } );
9313
9314
9315 // Order by the selected column(s)
9316 _api_register( [
9317 'columns().order()',
9318 'column().order()'
9319 ], function ( dir ) {
9320 var that = this;
9321
9322 if ( ! dir ) {
9323 return this.iterator( 'column', function ( settings, idx ) {
9324 var sort = _fnSortFlatten( settings );
9325
9326 for ( var i=0, ien=sort.length ; i<ien ; i++ ) {
9327 if ( sort[i].col === idx ) {
9328 return sort[i].dir;
9329 }
9330 }
9331
9332 return null;
9333 }, 1 );
9334 }
9335 else {
9336 return this.iterator( 'table', function ( settings, i ) {
9337 settings.aaSorting = that[i].map( function (col) {
9338 return [ col, dir ];
9339 } );
9340 } );
9341 }
9342 } );
9343
9344 _api_registerPlural('columns().orderable()', 'column().orderable()', function ( directions ) {
9345 return this.iterator( 'column', function ( settings, idx ) {
9346 var col = settings.aoColumns[idx];
9347
9348 return directions ?
9349 col.asSorting :
9350 col.bSortable;
9351 }, 1 );
9352 } );
9353
9354
9355 _api_register( 'processing()', function ( show ) {
9356 return this.iterator( 'table', function ( ctx ) {
9357 _fnProcessingDisplay( ctx, show );
9358 } );
9359 } );
9360
9361
9362 _api_register( 'search()', function ( input, regex, smart, caseInsen ) {
9363 var ctx = this.context;
9364
9365 if ( input === undefined ) {
9366 // get
9367 return ctx.length !== 0 ?
9368 ctx[0].oPreviousSearch.search :
9369 undefined;
9370 }
9371
9372 // set
9373 return this.iterator( 'table', function ( settings ) {
9374 if ( ! settings.oFeatures.bFilter ) {
9375 return;
9376 }
9377
9378 if (typeof regex === 'object') {
9379 // New style options to pass to the search builder
9380 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, regex, {
9381 search: input
9382 } ) );
9383 }
9384 else {
9385 // Compat for the old options
9386 _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, {
9387 search: input,
9388 regex: regex === null ? false : regex,
9389 smart: smart === null ? true : smart,
9390 caseInsensitive: caseInsen === null ? true : caseInsen
9391 } ) );
9392 }
9393 } );
9394 } );
9395
9396 _api_register( 'search.fixed()', function ( name, search ) {
9397 var ret = this.iterator( true, 'table', function ( settings ) {
9398 var fixed = settings.searchFixed;
9399
9400 if (! name) {
9401 return Object.keys(fixed)
9402 }
9403 else if (search === undefined) {
9404 return fixed[name];
9405 }
9406 else if (search === null) {
9407 delete fixed[name];
9408 }
9409 else {
9410 fixed[name] = search;
9411 }
9412
9413 return this;
9414 } );
9415
9416 return name !== undefined && search === undefined
9417 ? ret[0]
9418 : ret;
9419 } );
9420
9421 _api_registerPlural(
9422 'columns().search()',
9423 'column().search()',
9424 function ( input, regex, smart, caseInsen ) {
9425 return this.iterator( 'column', function ( settings, column ) {
9426 var preSearch = settings.aoPreSearchCols;
9427
9428 if ( input === undefined ) {
9429 // get
9430 return preSearch[ column ].search;
9431 }
9432
9433 // set
9434 if ( ! settings.oFeatures.bFilter ) {
9435 return;
9436 }
9437
9438 if (typeof regex === 'object') {
9439 // New style options to pass to the search builder
9440 $.extend( preSearch[ column ], regex, {
9441 search: input
9442 } );
9443 }
9444 else {
9445 // Old style (with not all options available)
9446 $.extend( preSearch[ column ], {
9447 search: input,
9448 regex: regex === null ? false : regex,
9449 smart: smart === null ? true : smart,
9450 caseInsensitive: caseInsen === null ? true : caseInsen
9451 } );
9452 }
9453
9454 _fnFilterComplete( settings, settings.oPreviousSearch );
9455 } );
9456 }
9457 );
9458
9459 _api_register([
9460 'columns().search.fixed()',
9461 'column().search.fixed()'
9462 ],
9463 function ( name, search ) {
9464 var ret = this.iterator( true, 'column', function ( settings, colIdx ) {
9465 var fixed = settings.aoColumns[colIdx].searchFixed;
9466
9467 if (! name) {
9468 return Object.keys(fixed)
9469 }
9470 else if (search === undefined) {
9471 return fixed[name];
9472 }
9473 else if (search === null) {
9474 delete fixed[name];
9475 }
9476 else {
9477 fixed[name] = search;
9478 }
9479
9480 return this;
9481 } );
9482
9483 return name !== undefined && search === undefined
9484 ? ret[0]
9485 : ret;
9486 }
9487 );
9488 /*
9489 * State API methods
9490 */
9491
9492 _api_register( 'state()', function ( set, ignoreTime ) {
9493 // getter
9494 if ( ! set ) {
9495 return this.context.length ?
9496 this.context[0].oSavedState :
9497 null;
9498 }
9499
9500 var setMutate = $.extend( true, {}, set );
9501
9502 // setter
9503 return this.iterator( 'table', function ( settings ) {
9504 if ( ignoreTime !== false ) {
9505 setMutate.time = +new Date() + 100;
9506 }
9507
9508 _fnImplementState( settings, setMutate, function(){} );
9509 } );
9510 } );
9511
9512
9513 _api_register( 'state.clear()', function () {
9514 return this.iterator( 'table', function ( settings ) {
9515 // Save an empty object
9516 settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
9517 } );
9518 } );
9519
9520
9521 _api_register( 'state.loaded()', function () {
9522 return this.context.length ?
9523 this.context[0].oLoadedState :
9524 null;
9525 } );
9526
9527
9528 _api_register( 'state.save()', function () {
9529 return this.iterator( 'table', function ( settings ) {
9530 _fnSaveState( settings );
9531 } );
9532 } );
9533
9534 // Can be assigned in DateTable.use() - note luxon and moment vars are in helpers.js
9535 var __bootstrap;
9536 var __foundation;
9537
9538 /**
9539 * Set the libraries that DataTables uses, or the global objects.
9540 * Note that the arguments can be either way around (legacy support)
9541 * and the second is optional. See docs.
9542 */
9543 DataTable.use = function (arg1, arg2) {
9544 // Reverse arguments for legacy support
9545 var module = typeof arg1 === 'string'
9546 ? arg2
9547 : arg1;
9548 var type = typeof arg2 === 'string'
9549 ? arg2
9550 : arg1;
9551
9552 // Getter
9553 if (module === undefined && typeof type === 'string') {
9554 switch (type) {
9555 case 'lib':
9556 case 'jq':
9557 return $;
9558
9559 case 'win':
9560 return window;
9561
9562 case 'datetime':
9563 return DataTable.DateTime;
9564
9565 case 'luxon':
9566 return __luxon;
9567
9568 case 'moment':
9569 return __moment;
9570
9571 case 'bootstrap':
9572 // Use local if set, otherwise try window, which could be undefined
9573 return __bootstrap || window.bootstrap;
9574
9575 case 'foundation':
9576 // Ditto
9577 return __foundation || window.Foundation;
9578
9579 default:
9580 return null;
9581 }
9582 }
9583
9584 // Setter
9585 if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) {
9586 $ = module;
9587 }
9588 else if (type === 'win' || (module && module.document)) {
9589 window = module;
9590 document = module.document;
9591 }
9592 else if (type === 'datetime' || (module && module.type === 'DateTime')) {
9593 DataTable.DateTime = module;
9594 }
9595 else if (type === 'luxon' || (module && module.FixedOffsetZone)) {
9596 __luxon = module;
9597 }
9598 else if (type === 'moment' || (module && module.isMoment)) {
9599 __moment = module;
9600 }
9601 else if (type === 'bootstrap' || (module && module.Modal && module.Modal.NAME === 'modal'))
9602 {
9603 // This is currently for BS5 only. BS3/4 attach to jQuery, so no need to use `.use()`
9604 __bootstrap = module;
9605 }
9606 else if (type === 'foundation' || (module && module.Reveal)) {
9607 __foundation = module;
9608 }
9609 }
9610
9611 /**
9612 * CommonJS factory function pass through. This will check if the arguments
9613 * given are a window object or a jQuery object. If so they are set
9614 * accordingly.
9615 * @param {*} root Window
9616 * @param {*} jq jQUery
9617 * @returns {boolean} Indicator
9618 */
9619 DataTable.factory = function (root, jq) {
9620 var is = false;
9621
9622 // Test if the first parameter is a window object
9623 if (root && root.document) {
9624 window = root;
9625 document = root.document;
9626 }
9627
9628 // Test if the second parameter is a jQuery object
9629 if (jq && jq.fn && jq.fn.jquery) {
9630 $ = jq;
9631 is = true;
9632 }
9633
9634 return is;
9635 }
9636
9637 /**
9638 * Provide a common method for plug-ins to check the version of DataTables being
9639 * used, in order to ensure compatibility.
9640 *
9641 * @param {string} version Version string to check for, in the format "X.Y.Z".
9642 * Note that the formats "X" and "X.Y" are also acceptable.
9643 * @param {string} [version2=current DataTables version] As above, but optional.
9644 * If not given the current DataTables version will be used.
9645 * @returns {boolean} true if this version of DataTables is greater or equal to
9646 * the required version, or false if this version of DataTales is not
9647 * suitable
9648 * @static
9649 * @dtopt API-Static
9650 *
9651 * @example
9652 * alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
9653 */
9654 DataTable.versionCheck = function( version, version2 )
9655 {
9656 var aThis = version2 ?
9657 version2.split('.') :
9658 DataTable.version.split('.');
9659 var aThat = version.split('.');
9660 var iThis, iThat;
9661
9662 for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
9663 iThis = parseInt( aThis[i], 10 ) || 0;
9664 iThat = parseInt( aThat[i], 10 ) || 0;
9665
9666 // Parts are the same, keep comparing
9667 if (iThis === iThat) {
9668 continue;
9669 }
9670
9671 // Parts are different, return immediately
9672 return iThis > iThat;
9673 }
9674
9675 return true;
9676 };
9677
9678
9679 /**
9680 * Check if a `<table>` node is a DataTable table already or not.
9681 *
9682 * @param {node|jquery|string} table Table node, jQuery object or jQuery
9683 * selector for the table to test. Note that if more than more than one
9684 * table is passed on, only the first will be checked
9685 * @returns {boolean} true the table given is a DataTable, or false otherwise
9686 * @static
9687 * @dtopt API-Static
9688 *
9689 * @example
9690 * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
9691 * $('#example').dataTable();
9692 * }
9693 */
9694 DataTable.isDataTable = function ( table )
9695 {
9696 var t = $(table).get(0);
9697 var is = false;
9698
9699 if ( table instanceof DataTable.Api ) {
9700 return true;
9701 }
9702
9703 $.each( DataTable.settings, function (i, o) {
9704 var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
9705 var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
9706
9707 if ( o.nTable === t || head === t || foot === t ) {
9708 is = true;
9709 }
9710 } );
9711
9712 return is;
9713 };
9714
9715
9716 /**
9717 * Get all DataTable tables that have been initialised - optionally you can
9718 * select to get only currently visible tables.
9719 *
9720 * @param {boolean} [visible=false] Flag to indicate if you want all (default)
9721 * or visible tables only.
9722 * @returns {array} Array of `table` nodes (not DataTable instances) which are
9723 * DataTables
9724 * @static
9725 * @dtopt API-Static
9726 *
9727 * @example
9728 * $.each( $.fn.dataTable.tables(true), function () {
9729 * $(table).DataTable().columns.adjust();
9730 * } );
9731 */
9732 DataTable.tables = function ( visible )
9733 {
9734 var api = false;
9735
9736 if ( $.isPlainObject( visible ) ) {
9737 api = visible.api;
9738 visible = visible.visible;
9739 }
9740
9741 var a = DataTable.settings
9742 .filter( function (o) {
9743 return !visible || (visible && $(o.nTable).is(':visible'))
9744 ? true
9745 : false;
9746 } )
9747 .map( function (o) {
9748 return o.nTable;
9749 });
9750
9751 return api ?
9752 new _Api( a ) :
9753 a;
9754 };
9755
9756
9757 /**
9758 * Convert from camel case parameters to Hungarian notation. This is made public
9759 * for the extensions to provide the same ability as DataTables core to accept
9760 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
9761 * parameters.
9762 *
9763 * @param {object} src The model object which holds all parameters that can be
9764 * mapped.
9765 * @param {object} user The object to convert from camel case to Hungarian.
9766 * @param {boolean} force When set to `true`, properties which already have a
9767 * Hungarian value in the `user` object will be overwritten. Otherwise they
9768 * won't be.
9769 */
9770 DataTable.camelToHungarian = _fnCamelToHungarian;
9771
9772
9773
9774 /**
9775 *
9776 */
9777 _api_register( '$()', function ( selector, opts ) {
9778 var
9779 rows = this.rows( opts ).nodes(), // Get all rows
9780 jqRows = $(rows);
9781
9782 return $( [].concat(
9783 jqRows.filter( selector ).toArray(),
9784 jqRows.find( selector ).toArray()
9785 ) );
9786 } );
9787
9788
9789 // jQuery functions to operate on the tables
9790 $.each( [ 'on', 'one', 'off' ], function (i, key) {
9791 _api_register( key+'()', function ( /* event, handler */ ) {
9792 var args = Array.prototype.slice.call(arguments);
9793
9794 // Add the `dt` namespace automatically if it isn't already present
9795 args[0] = args[0].split( /\s/ ).map( function ( e ) {
9796 return ! e.match(/\.dt\b/) ?
9797 e+'.dt' :
9798 e;
9799 } ).join( ' ' );
9800
9801 var inst = $( this.tables().nodes() );
9802 inst[key].apply( inst, args );
9803 return this;
9804 } );
9805 } );
9806
9807
9808 _api_register( 'clear()', function () {
9809 return this.iterator( 'table', function ( settings ) {
9810 _fnClearTable( settings );
9811 } );
9812 } );
9813
9814
9815 _api_register( 'error()', function (msg) {
9816 return this.iterator( 'table', function ( settings ) {
9817 _fnLog( settings, 0, msg );
9818 } );
9819 } );
9820
9821
9822 _api_register( 'settings()', function () {
9823 return new _Api( this.context, this.context );
9824 } );
9825
9826
9827 _api_register( 'init()', function () {
9828 var ctx = this.context;
9829 return ctx.length ? ctx[0].oInit : null;
9830 } );
9831
9832
9833 _api_register( 'data()', function () {
9834 return this.iterator( 'table', function ( settings ) {
9835 return _pluck( settings.aoData, '_aData' );
9836 } ).flatten();
9837 } );
9838
9839
9840 _api_register( 'trigger()', function ( name, args, bubbles ) {
9841 return this.iterator( 'table', function ( settings ) {
9842 return _fnCallbackFire( settings, null, name, args, bubbles );
9843 } ).flatten();
9844 } );
9845
9846
9847 _api_register( 'ready()', function ( fn ) {
9848 var ctx = this.context;
9849
9850 // Get status of first table
9851 if (! fn) {
9852 return ctx.length
9853 ? (ctx[0]._bInitComplete || false)
9854 : null;
9855 }
9856
9857 // Function to run either once the table becomes ready or
9858 // immediately if it is already ready.
9859 return this.tables().every(function () {
9860 var api = this;
9861
9862 if (this.context[0]._bInitComplete) {
9863 fn.call(api);
9864 }
9865 else {
9866 this.on('init.dt.DT', function () {
9867 fn.call(api);
9868 });
9869 }
9870 } );
9871 } );
9872
9873
9874 _api_register( 'destroy()', function ( remove ) {
9875 remove = remove || false;
9876
9877 return this.iterator( 'table', function ( settings ) {
9878 var classes = settings.oClasses;
9879 var table = settings.nTable;
9880 var tbody = settings.nTBody;
9881 var thead = settings.nTHead;
9882 var tfoot = settings.nTFoot;
9883 var jqTable = $(table);
9884 var jqTbody = $(tbody);
9885 var jqWrapper = $(settings.nTableWrapper);
9886 var rows = settings.aoData.map( function (r) { return r ? r.nTr : null; } );
9887 var orderClasses = classes.order;
9888
9889 // Flag to note that the table is currently being destroyed - no action
9890 // should be taken
9891 settings.bDestroying = true;
9892
9893 // Fire off the destroy callbacks for plug-ins etc
9894 _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings], true );
9895
9896 // If not being removed from the document, make all columns visible
9897 if ( ! remove ) {
9898 new _Api( settings ).columns().visible( true );
9899 }
9900
9901 // Container width change listener
9902 if (settings.resizeObserver) {
9903 settings.resizeObserver.disconnect();
9904 }
9905
9906 // Blitz all `DT` namespaced events (these are internal events, the
9907 // lowercase, `dt` events are user subscribed and they are responsible
9908 // for removing them
9909 jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
9910 $(window).off('.DT-'+settings.sInstance);
9911
9912 // When scrolling we had to break the table up - restore it
9913 if ( table != thead.parentNode ) {
9914 jqTable.children('thead').detach();
9915 jqTable.append( thead );
9916 }
9917
9918 if ( tfoot && table != tfoot.parentNode ) {
9919 jqTable.children('tfoot').detach();
9920 jqTable.append( tfoot );
9921 }
9922
9923 // Clean up the header
9924 $(thead).find('span.dt-column-order').remove();
9925 $(thead).find('span.dt-column-title').each(function () {
9926 var title = $(this).html();
9927 $(this).parent().append(title);
9928 $(this).remove();
9929 });
9930
9931 settings.colgroup.remove();
9932
9933 settings.aaSorting = [];
9934 settings.aaSortingFixed = [];
9935 _fnSortingClasses( settings );
9936
9937 $(jqTable).find('th, td').removeClass(
9938 $.map(DataTable.ext.type.className, function (v) {
9939 return v;
9940 }).join(' ')
9941 );
9942
9943 $('th, td', thead)
9944 .removeClass(
9945 orderClasses.none + ' ' +
9946 orderClasses.canAsc + ' ' +
9947 orderClasses.canDesc + ' ' +
9948 orderClasses.isAsc + ' ' +
9949 orderClasses.isDesc
9950 )
9951 .css('width', '')
9952 .removeAttr('data-dt-column')
9953 .removeAttr('aria-sort');
9954
9955 // Add the TR elements back into the table in their original order
9956 jqTbody.children().detach();
9957 jqTbody.append( rows );
9958
9959 var orig = settings.nTableWrapper.parentNode;
9960 var insertBefore = settings.nTableWrapper.nextSibling;
9961
9962 // Remove the DataTables generated nodes, events and classes
9963 var removedMethod = remove ? 'remove' : 'detach';
9964 jqTable[ removedMethod ]();
9965 jqWrapper[ removedMethod ]();
9966
9967 // If we need to reattach the table to the document
9968 if ( ! remove && orig ) {
9969 // insertBefore acts like appendChild if !arg[1]
9970 orig.insertBefore( table, insertBefore );
9971
9972 // Restore the width of the original table - was read from the style property,
9973 // so we can restore directly to that
9974 jqTable
9975 .css( 'width', settings.sDestroyWidth )
9976 .removeClass( classes.table );
9977 }
9978
9979 /* Remove the settings object from the settings array */
9980 var idx = DataTable.settings.indexOf(settings);
9981 if ( idx !== -1 ) {
9982 DataTable.settings.splice( idx, 1 );
9983 }
9984 } );
9985 } );
9986
9987
9988 // Add the `every()` method for rows, columns and cells in a compact form
9989 $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
9990 _api_register( type+'s().every()', function ( fn ) {
9991 var opts = this.selector.opts;
9992 var api = this;
9993 var inst;
9994 var counter = 0;
9995
9996 return this.iterator( 'every', function ( settings, selectedIdx, tableIdx ) {
9997 inst = api[ type ](selectedIdx, opts);
9998
9999 if (type === 'cell') {
10000 fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter);
10001 }
10002 else {
10003 fn.call(inst, selectedIdx, tableIdx, counter);
10004 }
10005
10006 counter++;
10007 } );
10008 } );
10009 } );
10010
10011
10012 // i18n method for extensions to be able to use the language object from the
10013 // DataTable
10014 _api_register( 'i18n()', function ( token, def, plural ) {
10015 var ctx = this.context[0];
10016 var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
10017
10018 if ( resolved === undefined ) {
10019 resolved = def;
10020 }
10021
10022 if ( $.isPlainObject( resolved ) ) {
10023 resolved = plural !== undefined && resolved[ plural ] !== undefined ?
10024 resolved[ plural ] :
10025 resolved._;
10026 }
10027
10028 return typeof resolved === 'string'
10029 ? resolved.replace( '%d', plural ) // nb: plural might be undefined,
10030 : resolved;
10031 } );
10032
10033 /**
10034 * Version string for plug-ins to check compatibility. Allowed format is
10035 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
10036 * only for non-release builds. See https://semver.org/ for more information.
10037 * @member
10038 * @type string
10039 * @default Version number
10040 */
10041 DataTable.version = "2.2.2";
10042
10043 /**
10044 * Private data store, containing all of the settings objects that are
10045 * created for the tables on a given page.
10046 *
10047 * Note that the `DataTable.settings` object is aliased to
10048 * `jQuery.fn.dataTableExt` through which it may be accessed and
10049 * manipulated, or `jQuery.fn.dataTable.settings`.
10050 * @member
10051 * @type array
10052 * @default []
10053 * @private
10054 */
10055 DataTable.settings = [];
10056
10057 /**
10058 * Object models container, for the various models that DataTables has
10059 * available to it. These models define the objects that are used to hold
10060 * the active state and configuration of the table.
10061 * @namespace
10062 */
10063 DataTable.models = {};
10064
10065
10066
10067 /**
10068 * Template object for the way in which DataTables holds information about
10069 * search information for the global filter and individual column filters.
10070 * @namespace
10071 */
10072 DataTable.models.oSearch = {
10073 /**
10074 * Flag to indicate if the filtering should be case insensitive or not
10075 */
10076 "caseInsensitive": true,
10077
10078 /**
10079 * Applied search term
10080 */
10081 "search": "",
10082
10083 /**
10084 * Flag to indicate if the search term should be interpreted as a
10085 * regular expression (true) or not (false) and therefore and special
10086 * regex characters escaped.
10087 */
10088 "regex": false,
10089
10090 /**
10091 * Flag to indicate if DataTables is to use its smart filtering or not.
10092 */
10093 "smart": true,
10094
10095 /**
10096 * Flag to indicate if DataTables should only trigger a search when
10097 * the return key is pressed.
10098 */
10099 "return": false
10100 };
10101
10102
10103
10104
10105 /**
10106 * Template object for the way in which DataTables holds information about
10107 * each individual row. This is the object format used for the settings
10108 * aoData array.
10109 * @namespace
10110 */
10111 DataTable.models.oRow = {
10112 /**
10113 * TR element for the row
10114 */
10115 "nTr": null,
10116
10117 /**
10118 * Array of TD elements for each row. This is null until the row has been
10119 * created.
10120 */
10121 "anCells": null,
10122
10123 /**
10124 * Data object from the original data source for the row. This is either
10125 * an array if using the traditional form of DataTables, or an object if
10126 * using mData options. The exact type will depend on the passed in
10127 * data from the data source, or will be an array if using DOM a data
10128 * source.
10129 */
10130 "_aData": [],
10131
10132 /**
10133 * Sorting data cache - this array is ostensibly the same length as the
10134 * number of columns (although each index is generated only as it is
10135 * needed), and holds the data that is used for sorting each column in the
10136 * row. We do this cache generation at the start of the sort in order that
10137 * the formatting of the sort data need be done only once for each cell
10138 * per sort. This array should not be read from or written to by anything
10139 * other than the master sorting methods.
10140 */
10141 "_aSortData": null,
10142
10143 /**
10144 * Per cell filtering data cache. As per the sort data cache, used to
10145 * increase the performance of the filtering in DataTables
10146 */
10147 "_aFilterData": null,
10148
10149 /**
10150 * Filtering data cache. This is the same as the cell filtering cache, but
10151 * in this case a string rather than an array. This is easily computed with
10152 * a join on `_aFilterData`, but is provided as a cache so the join isn't
10153 * needed on every search (memory traded for performance)
10154 */
10155 "_sFilterRow": null,
10156
10157 /**
10158 * Denote if the original data source was from the DOM, or the data source
10159 * object. This is used for invalidating data, so DataTables can
10160 * automatically read data from the original source, unless uninstructed
10161 * otherwise.
10162 */
10163 "src": null,
10164
10165 /**
10166 * Index in the aoData array. This saves an indexOf lookup when we have the
10167 * object, but want to know the index
10168 */
10169 "idx": -1,
10170
10171 /**
10172 * Cached display value
10173 */
10174 displayData: null
10175 };
10176
10177
10178 /**
10179 * Template object for the column information object in DataTables. This object
10180 * is held in the settings aoColumns array and contains all the information that
10181 * DataTables needs about each individual column.
10182 *
10183 * Note that this object is related to {@link DataTable.defaults.column}
10184 * but this one is the internal data store for DataTables's cache of columns.
10185 * It should NOT be manipulated outside of DataTables. Any configuration should
10186 * be done through the initialisation options.
10187 * @namespace
10188 */
10189 DataTable.models.oColumn = {
10190 /**
10191 * Column index.
10192 */
10193 "idx": null,
10194
10195 /**
10196 * A list of the columns that sorting should occur on when this column
10197 * is sorted. That this property is an array allows multi-column sorting
10198 * to be defined for a column (for example first name / last name columns
10199 * would benefit from this). The values are integers pointing to the
10200 * columns to be sorted on (typically it will be a single integer pointing
10201 * at itself, but that doesn't need to be the case).
10202 */
10203 "aDataSort": null,
10204
10205 /**
10206 * Define the sorting directions that are applied to the column, in sequence
10207 * as the column is repeatedly sorted upon - i.e. the first value is used
10208 * as the sorting direction when the column if first sorted (clicked on).
10209 * Sort it again (click again) and it will move on to the next index.
10210 * Repeat until loop.
10211 */
10212 "asSorting": null,
10213
10214 /**
10215 * Flag to indicate if the column is searchable, and thus should be included
10216 * in the filtering or not.
10217 */
10218 "bSearchable": null,
10219
10220 /**
10221 * Flag to indicate if the column is sortable or not.
10222 */
10223 "bSortable": null,
10224
10225 /**
10226 * Flag to indicate if the column is currently visible in the table or not
10227 */
10228 "bVisible": null,
10229
10230 /**
10231 * Store for manual type assignment using the `column.type` option. This
10232 * is held in store so we can manipulate the column's `sType` property.
10233 */
10234 "_sManualType": null,
10235
10236 /**
10237 * Flag to indicate if HTML5 data attributes should be used as the data
10238 * source for filtering or sorting. True is either are.
10239 */
10240 "_bAttrSrc": false,
10241
10242 /**
10243 * Developer definable function that is called whenever a cell is created (Ajax source,
10244 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
10245 * allowing you to modify the DOM element (add background colour for example) when the
10246 * element is available.
10247 */
10248 "fnCreatedCell": null,
10249
10250 /**
10251 * Function to get data from a cell in a column. You should <b>never</b>
10252 * access data directly through _aData internally in DataTables - always use
10253 * the method attached to this property. It allows mData to function as
10254 * required. This function is automatically assigned by the column
10255 * initialisation method
10256 */
10257 "fnGetData": null,
10258
10259 /**
10260 * Function to set data for a cell in the column. You should <b>never</b>
10261 * set the data directly to _aData internally in DataTables - always use
10262 * this method. It allows mData to function as required. This function
10263 * is automatically assigned by the column initialisation method
10264 */
10265 "fnSetData": null,
10266
10267 /**
10268 * Property to read the value for the cells in the column from the data
10269 * source array / object. If null, then the default content is used, if a
10270 * function is given then the return from the function is used.
10271 */
10272 "mData": null,
10273
10274 /**
10275 * Partner property to mData which is used (only when defined) to get
10276 * the data - i.e. it is basically the same as mData, but without the
10277 * 'set' option, and also the data fed to it is the result from mData.
10278 * This is the rendering method to match the data method of mData.
10279 */
10280 "mRender": null,
10281
10282 /**
10283 * The class to apply to all TD elements in the table's TBODY for the column
10284 */
10285 "sClass": null,
10286
10287 /**
10288 * When DataTables calculates the column widths to assign to each column,
10289 * it finds the longest string in each column and then constructs a
10290 * temporary table and reads the widths from that. The problem with this
10291 * is that "mmm" is much wider then "iiii", but the latter is a longer
10292 * string - thus the calculation can go wrong (doing it properly and putting
10293 * it into an DOM object and measuring that is horribly(!) slow). Thus as
10294 * a "work around" we provide this option. It will append its value to the
10295 * text that is found to be the longest string for the column - i.e. padding.
10296 */
10297 "sContentPadding": null,
10298
10299 /**
10300 * Allows a default value to be given for a column's data, and will be used
10301 * whenever a null data source is encountered (this can be because mData
10302 * is set to null, or because the data source itself is null).
10303 */
10304 "sDefaultContent": null,
10305
10306 /**
10307 * Name for the column, allowing reference to the column by name as well as
10308 * by index (needs a lookup to work by name).
10309 */
10310 "sName": null,
10311
10312 /**
10313 * Custom sorting data type - defines which of the available plug-ins in
10314 * afnSortData the custom sorting will use - if any is defined.
10315 */
10316 "sSortDataType": 'std',
10317
10318 /**
10319 * Class to be applied to the header element when sorting on this column
10320 */
10321 "sSortingClass": null,
10322
10323 /**
10324 * Title of the column - what is seen in the TH element (nTh).
10325 */
10326 "sTitle": null,
10327
10328 /**
10329 * Column sorting and filtering type
10330 */
10331 "sType": null,
10332
10333 /**
10334 * Width of the column
10335 */
10336 "sWidth": null,
10337
10338 /**
10339 * Width of the column when it was first "encountered"
10340 */
10341 "sWidthOrig": null,
10342
10343 /** Cached string which is the longest in the column */
10344 maxLenString: null,
10345
10346 /**
10347 * Store for named searches
10348 */
10349 searchFixed: null
10350 };
10351
10352
10353 /*
10354 * Developer note: The properties of the object below are given in Hungarian
10355 * notation, that was used as the interface for DataTables prior to v1.10, however
10356 * from v1.10 onwards the primary interface is camel case. In order to avoid
10357 * breaking backwards compatibility utterly with this change, the Hungarian
10358 * version is still, internally the primary interface, but is is not documented
10359 * - hence the @name tags in each doc comment. This allows a Javascript function
10360 * to create a map from Hungarian notation to camel case (going the other direction
10361 * would require each property to be listed, which would add around 3K to the size
10362 * of DataTables, while this method is about a 0.5K hit).
10363 *
10364 * Ultimately this does pave the way for Hungarian notation to be dropped
10365 * completely, but that is a massive amount of work and will break current
10366 * installs (therefore is on-hold until v2).
10367 */
10368
10369 /**
10370 * Initialisation options that can be given to DataTables at initialisation
10371 * time.
10372 * @namespace
10373 */
10374 DataTable.defaults = {
10375 /**
10376 * An array of data to use for the table, passed in at initialisation which
10377 * will be used in preference to any data which is already in the DOM. This is
10378 * particularly useful for constructing tables purely in Javascript, for
10379 * example with a custom Ajax call.
10380 */
10381 "aaData": null,
10382
10383
10384 /**
10385 * If ordering is enabled, then DataTables will perform a first pass sort on
10386 * initialisation. You can define which column(s) the sort is performed
10387 * upon, and the sorting direction, with this variable. The `sorting` array
10388 * should contain an array for each column to be sorted initially containing
10389 * the column's index and a direction string ('asc' or 'desc').
10390 */
10391 "aaSorting": [[0,'asc']],
10392
10393
10394 /**
10395 * This parameter is basically identical to the `sorting` parameter, but
10396 * cannot be overridden by user interaction with the table. What this means
10397 * is that you could have a column (visible or hidden) which the sorting
10398 * will always be forced on first - any sorting after that (from the user)
10399 * will then be performed as required. This can be useful for grouping rows
10400 * together.
10401 */
10402 "aaSortingFixed": [],
10403
10404
10405 /**
10406 * DataTables can be instructed to load data to display in the table from a
10407 * Ajax source. This option defines how that Ajax call is made and where to.
10408 *
10409 * The `ajax` property has three different modes of operation, depending on
10410 * how it is defined. These are:
10411 *
10412 * * `string` - Set the URL from where the data should be loaded from.
10413 * * `object` - Define properties for `jQuery.ajax`.
10414 * * `function` - Custom data get function
10415 *
10416 * `string`
10417 * --------
10418 *
10419 * As a string, the `ajax` property simply defines the URL from which
10420 * DataTables will load data.
10421 *
10422 * `object`
10423 * --------
10424 *
10425 * As an object, the parameters in the object are passed to
10426 * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control
10427 * of the Ajax request. DataTables has a number of default parameters which
10428 * you can override using this option. Please refer to the jQuery
10429 * documentation for a full description of the options available, although
10430 * the following parameters provide additional options in DataTables or
10431 * require special consideration:
10432 *
10433 * * `data` - As with jQuery, `data` can be provided as an object, but it
10434 * can also be used as a function to manipulate the data DataTables sends
10435 * to the server. The function takes a single parameter, an object of
10436 * parameters with the values that DataTables has readied for sending. An
10437 * object may be returned which will be merged into the DataTables
10438 * defaults, or you can add the items to the object that was passed in and
10439 * not return anything from the function. This supersedes `fnServerParams`
10440 * from DataTables 1.9-.
10441 *
10442 * * `dataSrc` - By default DataTables will look for the property `data` (or
10443 * `aaData` for compatibility with DataTables 1.9-) when obtaining data
10444 * from an Ajax source or for server-side processing - this parameter
10445 * allows that property to be changed. You can use Javascript dotted
10446 * object notation to get a data source for multiple levels of nesting, or
10447 * it my be used as a function. As a function it takes a single parameter,
10448 * the JSON returned from the server, which can be manipulated as
10449 * required, with the returned value being that used by DataTables as the
10450 * data source for the table.
10451 *
10452 * * `success` - Should not be overridden it is used internally in
10453 * DataTables. To manipulate / transform the data returned by the server
10454 * use `ajax.dataSrc`, or use `ajax` as a function (see below).
10455 *
10456 * `function`
10457 * ----------
10458 *
10459 * As a function, making the Ajax call is left up to yourself allowing
10460 * complete control of the Ajax request. Indeed, if desired, a method other
10461 * than Ajax could be used to obtain the required data, such as Web storage
10462 * or an AIR database.
10463 *
10464 * The function is given four parameters and no return is required. The
10465 * parameters are:
10466 *
10467 * 1. _object_ - Data to send to the server
10468 * 2. _function_ - Callback function that must be executed when the required
10469 * data has been obtained. That data should be passed into the callback
10470 * as the only parameter
10471 * 3. _object_ - DataTables settings object for the table
10472 */
10473 "ajax": null,
10474
10475
10476 /**
10477 * This parameter allows you to readily specify the entries in the length drop
10478 * down menu that DataTables shows when pagination is enabled. It can be
10479 * either a 1D array of options which will be used for both the displayed
10480 * option and the value, or a 2D array which will use the array in the first
10481 * position as the value, and the array in the second position as the
10482 * displayed options (useful for language strings such as 'All').
10483 *
10484 * Note that the `pageLength` property will be automatically set to the
10485 * first value given in this array, unless `pageLength` is also provided.
10486 */
10487 "aLengthMenu": [ 10, 25, 50, 100 ],
10488
10489
10490 /**
10491 * The `columns` option in the initialisation parameter allows you to define
10492 * details about the way individual columns behave. For a full list of
10493 * column options that can be set, please see
10494 * {@link DataTable.defaults.column}. Note that if you use `columns` to
10495 * define your columns, you must have an entry in the array for every single
10496 * column that you have in your table (these can be null if you don't which
10497 * to specify any options).
10498 */
10499 "aoColumns": null,
10500
10501 /**
10502 * Very similar to `columns`, `columnDefs` allows you to target a specific
10503 * column, multiple columns, or all columns, using the `targets` property of
10504 * each object in the array. This allows great flexibility when creating
10505 * tables, as the `columnDefs` arrays can be of any length, targeting the
10506 * columns you specifically want. `columnDefs` may use any of the column
10507 * options available: {@link DataTable.defaults.column}, but it _must_
10508 * have `targets` defined in each object in the array. Values in the `targets`
10509 * array may be:
10510 * <ul>
10511 * <li>a string - class name will be matched on the TH for the column</li>
10512 * <li>0 or a positive integer - column index counting from the left</li>
10513 * <li>a negative integer - column index counting from the right</li>
10514 * <li>the string "_all" - all columns (i.e. assign a default)</li>
10515 * </ul>
10516 */
10517 "aoColumnDefs": null,
10518
10519
10520 /**
10521 * Basically the same as `search`, this parameter defines the individual column
10522 * filtering state at initialisation time. The array must be of the same size
10523 * as the number of columns, and each element be an object with the parameters
10524 * `search` and `escapeRegex` (the latter is optional). 'null' is also
10525 * accepted and the default will be used.
10526 */
10527 "aoSearchCols": [],
10528
10529
10530 /**
10531 * Enable or disable automatic column width calculation. This can be disabled
10532 * as an optimisation (it takes some time to calculate the widths) if the
10533 * tables widths are passed in using `columns`.
10534 */
10535 "bAutoWidth": true,
10536
10537
10538 /**
10539 * Deferred rendering can provide DataTables with a huge speed boost when you
10540 * are using an Ajax or JS data source for the table. This option, when set to
10541 * true, will cause DataTables to defer the creation of the table elements for
10542 * each row until they are needed for a draw - saving a significant amount of
10543 * time.
10544 */
10545 "bDeferRender": true,
10546
10547
10548 /**
10549 * Replace a DataTable which matches the given selector and replace it with
10550 * one which has the properties of the new initialisation object passed. If no
10551 * table matches the selector, then the new DataTable will be constructed as
10552 * per normal.
10553 */
10554 "bDestroy": false,
10555
10556
10557 /**
10558 * Enable or disable filtering of data. Filtering in DataTables is "smart" in
10559 * that it allows the end user to input multiple words (space separated) and
10560 * will match a row containing those words, even if not in the order that was
10561 * specified (this allow matching across multiple columns). Note that if you
10562 * wish to use filtering in DataTables this must remain 'true' - to remove the
10563 * default filtering input box and retain filtering abilities, please use
10564 * {@link DataTable.defaults.dom}.
10565 */
10566 "bFilter": true,
10567
10568 /**
10569 * Used only for compatiblity with DT1
10570 * @deprecated
10571 */
10572 "bInfo": true,
10573
10574 /**
10575 * Used only for compatiblity with DT1
10576 * @deprecated
10577 */
10578 "bLengthChange": true,
10579
10580 /**
10581 * Enable or disable pagination.
10582 */
10583 "bPaginate": true,
10584
10585
10586 /**
10587 * Enable or disable the display of a 'processing' indicator when the table is
10588 * being processed (e.g. a sort). This is particularly useful for tables with
10589 * large amounts of data where it can take a noticeable amount of time to sort
10590 * the entries.
10591 */
10592 "bProcessing": false,
10593
10594
10595 /**
10596 * Retrieve the DataTables object for the given selector. Note that if the
10597 * table has already been initialised, this parameter will cause DataTables
10598 * to simply return the object that has already been set up - it will not take
10599 * account of any changes you might have made to the initialisation object
10600 * passed to DataTables (setting this parameter to true is an acknowledgement
10601 * that you understand this). `destroy` can be used to reinitialise a table if
10602 * you need.
10603 */
10604 "bRetrieve": false,
10605
10606
10607 /**
10608 * When vertical (y) scrolling is enabled, DataTables will force the height of
10609 * the table's viewport to the given height at all times (useful for layout).
10610 * However, this can look odd when filtering data down to a small data set,
10611 * and the footer is left "floating" further down. This parameter (when
10612 * enabled) will cause DataTables to collapse the table's viewport down when
10613 * the result set will fit within the given Y height.
10614 */
10615 "bScrollCollapse": false,
10616
10617
10618 /**
10619 * Configure DataTables to use server-side processing. Note that the
10620 * `ajax` parameter must also be given in order to give DataTables a
10621 * source to obtain the required data for each draw.
10622 */
10623 "bServerSide": false,
10624
10625
10626 /**
10627 * Enable or disable sorting of columns. Sorting of individual columns can be
10628 * disabled by the `sortable` option for each column.
10629 */
10630 "bSort": true,
10631
10632
10633 /**
10634 * Enable or display DataTables' ability to sort multiple columns at the
10635 * same time (activated by shift-click by the user).
10636 */
10637 "bSortMulti": true,
10638
10639
10640 /**
10641 * Allows control over whether DataTables should use the top (true) unique
10642 * cell that is found for a single column, or the bottom (false - default).
10643 * This is useful when using complex headers.
10644 */
10645 "bSortCellsTop": null,
10646
10647
10648 /**
10649 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
10650 * `sorting\_3` to the columns which are currently being sorted on. This is
10651 * presented as a feature switch as it can increase processing time (while
10652 * classes are removed and added) so for large data sets you might want to
10653 * turn this off.
10654 */
10655 "bSortClasses": true,
10656
10657
10658 /**
10659 * Enable or disable state saving. When enabled HTML5 `localStorage` will be
10660 * used to save table display information such as pagination information,
10661 * display length, filtering and sorting. As such when the end user reloads
10662 * the page the display display will match what thy had previously set up.
10663 */
10664 "bStateSave": false,
10665
10666
10667 /**
10668 * This function is called when a TR element is created (and all TD child
10669 * elements have been inserted), or registered if using a DOM source, allowing
10670 * manipulation of the TR element (adding classes etc).
10671 */
10672 "fnCreatedRow": null,
10673
10674
10675 /**
10676 * This function is called on every 'draw' event, and allows you to
10677 * dynamically modify any aspect you want about the created DOM.
10678 */
10679 "fnDrawCallback": null,
10680
10681
10682 /**
10683 * Identical to fnHeaderCallback() but for the table footer this function
10684 * allows you to modify the table footer on every 'draw' event.
10685 */
10686 "fnFooterCallback": null,
10687
10688
10689 /**
10690 * When rendering large numbers in the information element for the table
10691 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
10692 * to have a comma separator for the 'thousands' units (e.g. 1 million is
10693 * rendered as "1,000,000") to help readability for the end user. This
10694 * function will override the default method DataTables uses.
10695 */
10696 "fnFormatNumber": function ( toFormat ) {
10697 return toFormat.toString().replace(
10698 /\B(?=(\d{3})+(?!\d))/g,
10699 this.oLanguage.sThousands
10700 );
10701 },
10702
10703
10704 /**
10705 * This function is called on every 'draw' event, and allows you to
10706 * dynamically modify the header row. This can be used to calculate and
10707 * display useful information about the table.
10708 */
10709 "fnHeaderCallback": null,
10710
10711
10712 /**
10713 * The information element can be used to convey information about the current
10714 * state of the table. Although the internationalisation options presented by
10715 * DataTables are quite capable of dealing with most customisations, there may
10716 * be times where you wish to customise the string further. This callback
10717 * allows you to do exactly that.
10718 */
10719 "fnInfoCallback": null,
10720
10721
10722 /**
10723 * Called when the table has been initialised. Normally DataTables will
10724 * initialise sequentially and there will be no need for this function,
10725 * however, this does not hold true when using external language information
10726 * since that is obtained using an async XHR call.
10727 */
10728 "fnInitComplete": null,
10729
10730
10731 /**
10732 * Called at the very start of each table draw and can be used to cancel the
10733 * draw by returning false, any other return (including undefined) results in
10734 * the full draw occurring).
10735 */
10736 "fnPreDrawCallback": null,
10737
10738
10739 /**
10740 * This function allows you to 'post process' each row after it have been
10741 * generated for each table draw, but before it is rendered on screen. This
10742 * function might be used for setting the row class name etc.
10743 */
10744 "fnRowCallback": null,
10745
10746
10747 /**
10748 * Load the table state. With this function you can define from where, and how, the
10749 * state of a table is loaded. By default DataTables will load from `localStorage`
10750 * but you might wish to use a server-side database or cookies.
10751 */
10752 "fnStateLoadCallback": function ( settings ) {
10753 try {
10754 return JSON.parse(
10755 (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
10756 'DataTables_'+settings.sInstance+'_'+location.pathname
10757 )
10758 );
10759 } catch (e) {
10760 return {};
10761 }
10762 },
10763
10764
10765 /**
10766 * Callback which allows modification of the saved state prior to loading that state.
10767 * This callback is called when the table is loading state from the stored data, but
10768 * prior to the settings object being modified by the saved state. Note that for
10769 * plug-in authors, you should use the `stateLoadParams` event to load parameters for
10770 * a plug-in.
10771 */
10772 "fnStateLoadParams": null,
10773
10774
10775 /**
10776 * Callback that is called when the state has been loaded from the state saving method
10777 * and the DataTables settings object has been modified as a result of the loaded state.
10778 */
10779 "fnStateLoaded": null,
10780
10781
10782 /**
10783 * Save the table state. This function allows you to define where and how the state
10784 * information for the table is stored By default DataTables will use `localStorage`
10785 * but you might wish to use a server-side database or cookies.
10786 */
10787 "fnStateSaveCallback": function ( settings, data ) {
10788 try {
10789 (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
10790 'DataTables_'+settings.sInstance+'_'+location.pathname,
10791 JSON.stringify( data )
10792 );
10793 } catch (e) {
10794 // noop
10795 }
10796 },
10797
10798
10799 /**
10800 * Callback which allows modification of the state to be saved. Called when the table
10801 * has changed state a new state save is required. This method allows modification of
10802 * the state saving object prior to actually doing the save, including addition or
10803 * other state properties or modification. Note that for plug-in authors, you should
10804 * use the `stateSaveParams` event to save parameters for a plug-in.
10805 */
10806 "fnStateSaveParams": null,
10807
10808
10809 /**
10810 * Duration for which the saved state information is considered valid. After this period
10811 * has elapsed the state will be returned to the default.
10812 * Value is given in seconds.
10813 */
10814 "iStateDuration": 7200,
10815
10816
10817 /**
10818 * Number of rows to display on a single page when using pagination. If
10819 * feature enabled (`lengthChange`) then the end user will be able to override
10820 * this to a custom setting using a pop-up menu.
10821 */
10822 "iDisplayLength": 10,
10823
10824
10825 /**
10826 * Define the starting point for data display when using DataTables with
10827 * pagination. Note that this parameter is the number of records, rather than
10828 * the page number, so if you have 10 records per page and want to start on
10829 * the third page, it should be "20".
10830 */
10831 "iDisplayStart": 0,
10832
10833
10834 /**
10835 * By default DataTables allows keyboard navigation of the table (sorting, paging,
10836 * and filtering) by adding a `tabindex` attribute to the required elements. This
10837 * allows you to tab through the controls and press the enter key to activate them.
10838 * The tabindex is default 0, meaning that the tab follows the flow of the document.
10839 * You can overrule this using this parameter if you wish. Use a value of -1 to
10840 * disable built-in keyboard navigation.
10841 */
10842 "iTabIndex": 0,
10843
10844
10845 /**
10846 * Classes that DataTables assigns to the various components and features
10847 * that it adds to the HTML table. This allows classes to be configured
10848 * during initialisation in addition to through the static
10849 * {@link DataTable.ext.oStdClasses} object).
10850 */
10851 "oClasses": {},
10852
10853
10854 /**
10855 * All strings that DataTables uses in the user interface that it creates
10856 * are defined in this object, allowing you to modified them individually or
10857 * completely replace them all as required.
10858 */
10859 "oLanguage": {
10860 /**
10861 * Strings that are used for WAI-ARIA labels and controls only (these are not
10862 * actually visible on the page, but will be read by screenreaders, and thus
10863 * must be internationalised as well).
10864 */
10865 "oAria": {
10866 /**
10867 * ARIA label that is added to the table headers when the column may be sorted
10868 */
10869 "orderable": ": Activate to sort",
10870
10871 /**
10872 * ARIA label that is added to the table headers when the column is currently being sorted
10873 */
10874 "orderableReverse": ": Activate to invert sorting",
10875
10876 /**
10877 * ARIA label that is added to the table headers when the column is currently being
10878 * sorted and next step is to remove sorting
10879 */
10880 "orderableRemove": ": Activate to remove sorting",
10881
10882 paginate: {
10883 first: 'First',
10884 last: 'Last',
10885 next: 'Next',
10886 previous: 'Previous',
10887 number: ''
10888 }
10889 },
10890
10891 /**
10892 * Pagination string used by DataTables for the built-in pagination
10893 * control types.
10894 */
10895 "oPaginate": {
10896 /**
10897 * Label and character for first page button («)
10898 */
10899 "sFirst": "\u00AB",
10900
10901 /**
10902 * Last page button (»)
10903 */
10904 "sLast": "\u00BB",
10905
10906 /**
10907 * Next page button (›)
10908 */
10909 "sNext": "\u203A",
10910
10911 /**
10912 * Previous page button (‹)
10913 */
10914 "sPrevious": "\u2039",
10915 },
10916
10917 /**
10918 * Plural object for the data type the table is showing
10919 */
10920 entries: {
10921 _: "entries",
10922 1: "entry"
10923 },
10924
10925 /**
10926 * This string is shown in preference to `zeroRecords` when the table is
10927 * empty of data (regardless of filtering). Note that this is an optional
10928 * parameter - if it is not given, the value of `zeroRecords` will be used
10929 * instead (either the default or given value).
10930 */
10931 "sEmptyTable": "No data available in table",
10932
10933
10934 /**
10935 * This string gives information to the end user about the information
10936 * that is current on display on the page. The following tokens can be
10937 * used in the string and will be dynamically replaced as the table
10938 * display updates. This tokens can be placed anywhere in the string, or
10939 * removed as needed by the language requires:
10940 *
10941 * * `\_START\_` - Display index of the first record on the current page
10942 * * `\_END\_` - Display index of the last record on the current page
10943 * * `\_TOTAL\_` - Number of records in the table after filtering
10944 * * `\_MAX\_` - Number of records in the table without filtering
10945 * * `\_PAGE\_` - Current page number
10946 * * `\_PAGES\_` - Total number of pages of data in the table
10947 */
10948 "sInfo": "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_",
10949
10950
10951 /**
10952 * Display information string for when the table is empty. Typically the
10953 * format of this string should match `info`.
10954 */
10955 "sInfoEmpty": "Showing 0 to 0 of 0 _ENTRIES-TOTAL_",
10956
10957
10958 /**
10959 * When a user filters the information in a table, this string is appended
10960 * to the information (`info`) to give an idea of how strong the filtering
10961 * is. The variable _MAX_ is dynamically updated.
10962 */
10963 "sInfoFiltered": "(filtered from _MAX_ total _ENTRIES-MAX_)",
10964
10965
10966 /**
10967 * If can be useful to append extra information to the info string at times,
10968 * and this variable does exactly that. This information will be appended to
10969 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
10970 * being used) at all times.
10971 */
10972 "sInfoPostFix": "",
10973
10974
10975 /**
10976 * This decimal place operator is a little different from the other
10977 * language options since DataTables doesn't output floating point
10978 * numbers, so it won't ever use this for display of a number. Rather,
10979 * what this parameter does is modify the sort methods of the table so
10980 * that numbers which are in a format which has a character other than
10981 * a period (`.`) as a decimal place will be sorted numerically.
10982 *
10983 * Note that numbers with different decimal places cannot be shown in
10984 * the same table and still be sortable, the table must be consistent.
10985 * However, multiple different tables on the page can use different
10986 * decimal place characters.
10987 */
10988 "sDecimal": "",
10989
10990
10991 /**
10992 * DataTables has a build in number formatter (`formatNumber`) which is
10993 * used to format large numbers that are used in the table information.
10994 * By default a comma is used, but this can be trivially changed to any
10995 * character you wish with this parameter.
10996 */
10997 "sThousands": ",",
10998
10999
11000 /**
11001 * Detail the action that will be taken when the drop down menu for the
11002 * pagination length option is changed. The '_MENU_' variable is replaced
11003 * with a default select list of 10, 25, 50 and 100, and can be replaced
11004 * with a custom select box if required.
11005 */
11006 "sLengthMenu": "_MENU_ _ENTRIES_ per page",
11007
11008
11009 /**
11010 * When using Ajax sourced data and during the first draw when DataTables is
11011 * gathering the data, this message is shown in an empty row in the table to
11012 * indicate to the end user the the data is being loaded. Note that this
11013 * parameter is not used when loading data by server-side processing, just
11014 * Ajax sourced data with client-side processing.
11015 */
11016 "sLoadingRecords": "Loading...",
11017
11018
11019 /**
11020 * Text which is displayed when the table is processing a user action
11021 * (usually a sort command or similar).
11022 */
11023 "sProcessing": "",
11024
11025
11026 /**
11027 * Details the actions that will be taken when the user types into the
11028 * filtering input text box. The variable "_INPUT_", if used in the string,
11029 * is replaced with the HTML text box for the filtering input allowing
11030 * control over where it appears in the string. If "_INPUT_" is not given
11031 * then the input box is appended to the string automatically.
11032 */
11033 "sSearch": "Search:",
11034
11035
11036 /**
11037 * Assign a `placeholder` attribute to the search `input` element
11038 * @type string
11039 * @default
11040 *
11041 * @dtopt Language
11042 * @name DataTable.defaults.language.searchPlaceholder
11043 */
11044 "sSearchPlaceholder": "",
11045
11046
11047 /**
11048 * All of the language information can be stored in a file on the
11049 * server-side, which DataTables will look up if this parameter is passed.
11050 * It must store the URL of the language file, which is in a JSON format,
11051 * and the object has the same properties as the oLanguage object in the
11052 * initialiser object (i.e. the above parameters). Please refer to one of
11053 * the example language files to see how this works in action.
11054 */
11055 "sUrl": "",
11056
11057
11058 /**
11059 * Text shown inside the table records when the is no information to be
11060 * displayed after filtering. `emptyTable` is shown when there is simply no
11061 * information in the table at all (regardless of filtering).
11062 */
11063 "sZeroRecords": "No matching records found"
11064 },
11065
11066
11067 /** The initial data order is reversed when `desc` ordering */
11068 orderDescReverse: true,
11069
11070
11071 /**
11072 * This parameter allows you to have define the global filtering state at
11073 * initialisation time. As an object the `search` parameter must be
11074 * defined, but all other parameters are optional. When `regex` is true,
11075 * the search string will be treated as a regular expression, when false
11076 * (default) it will be treated as a straight string. When `smart`
11077 * DataTables will use it's smart filtering methods (to word match at
11078 * any point in the data), when false this will not be done.
11079 */
11080 "oSearch": $.extend( {}, DataTable.models.oSearch ),
11081
11082
11083 /**
11084 * Table and control layout. This replaces the legacy `dom` option.
11085 */
11086 layout: {
11087 topStart: 'pageLength',
11088 topEnd: 'search',
11089 bottomStart: 'info',
11090 bottomEnd: 'paging'
11091 },
11092
11093
11094 /**
11095 * Legacy DOM layout option
11096 */
11097 "sDom": null,
11098
11099
11100 /**
11101 * Search delay option. This will throttle full table searches that use the
11102 * DataTables provided search input element (it does not effect calls to
11103 * `dt-api search()`, providing a delay before the search is made.
11104 */
11105 "searchDelay": null,
11106
11107
11108 /**
11109 * DataTables features six different built-in options for the buttons to
11110 * display for pagination control:
11111 *
11112 * * `numbers` - Page number buttons only
11113 * * `simple` - 'Previous' and 'Next' buttons only
11114 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
11115 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
11116 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
11117 * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
11118 */
11119 "sPaginationType": "",
11120
11121
11122 /**
11123 * Enable horizontal scrolling. When a table is too wide to fit into a
11124 * certain layout, or you have a large number of columns in the table, you
11125 * can enable x-scrolling to show the table in a viewport, which can be
11126 * scrolled. This property can be `true` which will allow the table to
11127 * scroll horizontally when needed, or any CSS unit, or a number (in which
11128 * case it will be treated as a pixel measurement). Setting as simply `true`
11129 * is recommended.
11130 */
11131 "sScrollX": "",
11132
11133
11134 /**
11135 * This property can be used to force a DataTable to use more width than it
11136 * might otherwise do when x-scrolling is enabled. For example if you have a
11137 * table which requires to be well spaced, this parameter is useful for
11138 * "over-sizing" the table, and thus forcing scrolling. This property can by
11139 * any CSS unit, or a number (in which case it will be treated as a pixel
11140 * measurement).
11141 */
11142 "sScrollXInner": "",
11143
11144
11145 /**
11146 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
11147 * to the given height, and enable scrolling for any data which overflows the
11148 * current viewport. This can be used as an alternative to paging to display
11149 * a lot of data in a small area (although paging and scrolling can both be
11150 * enabled at the same time). This property can be any CSS unit, or a number
11151 * (in which case it will be treated as a pixel measurement).
11152 */
11153 "sScrollY": "",
11154
11155
11156 /**
11157 * __Deprecated__ The functionality provided by this parameter has now been
11158 * superseded by that provided through `ajax`, which should be used instead.
11159 *
11160 * Set the HTTP method that is used to make the Ajax call for server-side
11161 * processing or Ajax sourced data.
11162 */
11163 "sServerMethod": "GET",
11164
11165
11166 /**
11167 * DataTables makes use of renderers when displaying HTML elements for
11168 * a table. These renderers can be added or modified by plug-ins to
11169 * generate suitable mark-up for a site. For example the Bootstrap
11170 * integration plug-in for DataTables uses a paging button renderer to
11171 * display pagination buttons in the mark-up required by Bootstrap.
11172 *
11173 * For further information about the renderers available see
11174 * DataTable.ext.renderer
11175 */
11176 "renderer": null,
11177
11178
11179 /**
11180 * Set the data property name that DataTables should use to get a row's id
11181 * to set as the `id` property in the node.
11182 */
11183 "rowId": "DT_RowId",
11184
11185
11186 /**
11187 * Caption value
11188 */
11189 "caption": null,
11190
11191
11192 /**
11193 * For server-side processing - use the data from the DOM for the first draw
11194 */
11195 iDeferLoading: null
11196 };
11197
11198 _fnHungarianMap( DataTable.defaults );
11199
11200
11201
11202 /*
11203 * Developer note - See note in model.defaults.js about the use of Hungarian
11204 * notation and camel case.
11205 */
11206
11207 /**
11208 * Column options that can be given to DataTables at initialisation time.
11209 * @namespace
11210 */
11211 DataTable.defaults.column = {
11212 /**
11213 * Define which column(s) an order will occur on for this column. This
11214 * allows a column's ordering to take multiple columns into account when
11215 * doing a sort or use the data from a different column. For example first
11216 * name / last name columns make sense to do a multi-column sort over the
11217 * two columns.
11218 */
11219 "aDataSort": null,
11220 "iDataSort": -1,
11221
11222 ariaTitle: '',
11223
11224
11225 /**
11226 * You can control the default ordering direction, and even alter the
11227 * behaviour of the sort handler (i.e. only allow ascending ordering etc)
11228 * using this parameter.
11229 */
11230 "asSorting": [ 'asc', 'desc', '' ],
11231
11232
11233 /**
11234 * Enable or disable filtering on the data in this column.
11235 */
11236 "bSearchable": true,
11237
11238
11239 /**
11240 * Enable or disable ordering on this column.
11241 */
11242 "bSortable": true,
11243
11244
11245 /**
11246 * Enable or disable the display of this column.
11247 */
11248 "bVisible": true,
11249
11250
11251 /**
11252 * Developer definable function that is called whenever a cell is created (Ajax source,
11253 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
11254 * allowing you to modify the DOM element (add background colour for example) when the
11255 * element is available.
11256 */
11257 "fnCreatedCell": null,
11258
11259
11260 /**
11261 * This property can be used to read data from any data source property,
11262 * including deeply nested objects / properties. `data` can be given in a
11263 * number of different ways which effect its behaviour:
11264 *
11265 * * `integer` - treated as an array index for the data source. This is the
11266 * default that DataTables uses (incrementally increased for each column).
11267 * * `string` - read an object property from the data source. There are
11268 * three 'special' options that can be used in the string to alter how
11269 * DataTables reads the data from the source object:
11270 * * `.` - Dotted Javascript notation. Just as you use a `.` in
11271 * Javascript to read from nested objects, so to can the options
11272 * specified in `data`. For example: `browser.version` or
11273 * `browser.name`. If your object parameter name contains a period, use
11274 * `\\` to escape it - i.e. `first\\.name`.
11275 * * `[]` - Array notation. DataTables can automatically combine data
11276 * from and array source, joining the data with the characters provided
11277 * between the two brackets. For example: `name[, ]` would provide a
11278 * comma-space separated list from the source array. If no characters
11279 * are provided between the brackets, the original array source is
11280 * returned.
11281 * * `()` - Function notation. Adding `()` to the end of a parameter will
11282 * execute a function of the name given. For example: `browser()` for a
11283 * simple function on the data source, `browser.version()` for a
11284 * function in a nested property or even `browser().version` to get an
11285 * object property if the function called returns an object. Note that
11286 * function notation is recommended for use in `render` rather than
11287 * `data` as it is much simpler to use as a renderer.
11288 * * `null` - use the original data source for the row rather than plucking
11289 * data directly from it. This action has effects on two other
11290 * initialisation options:
11291 * * `defaultContent` - When null is given as the `data` option and
11292 * `defaultContent` is specified for the column, the value defined by
11293 * `defaultContent` will be used for the cell.
11294 * * `render` - When null is used for the `data` option and the `render`
11295 * option is specified for the column, the whole data source for the
11296 * row is used for the renderer.
11297 * * `function` - the function given will be executed whenever DataTables
11298 * needs to set or get the data for a cell in the column. The function
11299 * takes three parameters:
11300 * * Parameters:
11301 * * `{array|object}` The data source for the row
11302 * * `{string}` The type call data requested - this will be 'set' when
11303 * setting data or 'filter', 'display', 'type', 'sort' or undefined
11304 * when gathering data. Note that when `undefined` is given for the
11305 * type DataTables expects to get the raw data for the object back<
11306 * * `{*}` Data to set when the second parameter is 'set'.
11307 * * Return:
11308 * * The return value from the function is not required when 'set' is
11309 * the type of call, but otherwise the return is what will be used
11310 * for the data requested.
11311 *
11312 * Note that `data` is a getter and setter option. If you just require
11313 * formatting of data for output, you will likely want to use `render` which
11314 * is simply a getter and thus simpler to use.
11315 *
11316 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
11317 * name change reflects the flexibility of this property and is consistent
11318 * with the naming of mRender. If 'mDataProp' is given, then it will still
11319 * be used by DataTables, as it automatically maps the old name to the new
11320 * if required.
11321 */
11322 "mData": null,
11323
11324
11325 /**
11326 * This property is the rendering partner to `data` and it is suggested that
11327 * when you want to manipulate data for display (including filtering,
11328 * sorting etc) without altering the underlying data for the table, use this
11329 * property. `render` can be considered to be the the read only companion to
11330 * `data` which is read / write (then as such more complex). Like `data`
11331 * this option can be given in a number of different ways to effect its
11332 * behaviour:
11333 *
11334 * * `integer` - treated as an array index for the data source. This is the
11335 * default that DataTables uses (incrementally increased for each column).
11336 * * `string` - read an object property from the data source. There are
11337 * three 'special' options that can be used in the string to alter how
11338 * DataTables reads the data from the source object:
11339 * * `.` - Dotted Javascript notation. Just as you use a `.` in
11340 * Javascript to read from nested objects, so to can the options
11341 * specified in `data`. For example: `browser.version` or
11342 * `browser.name`. If your object parameter name contains a period, use
11343 * `\\` to escape it - i.e. `first\\.name`.
11344 * * `[]` - Array notation. DataTables can automatically combine data
11345 * from and array source, joining the data with the characters provided
11346 * between the two brackets. For example: `name[, ]` would provide a
11347 * comma-space separated list from the source array. If no characters
11348 * are provided between the brackets, the original array source is
11349 * returned.
11350 * * `()` - Function notation. Adding `()` to the end of a parameter will
11351 * execute a function of the name given. For example: `browser()` for a
11352 * simple function on the data source, `browser.version()` for a
11353 * function in a nested property or even `browser().version` to get an
11354 * object property if the function called returns an object.
11355 * * `object` - use different data for the different data types requested by
11356 * DataTables ('filter', 'display', 'type' or 'sort'). The property names
11357 * of the object is the data type the property refers to and the value can
11358 * defined using an integer, string or function using the same rules as
11359 * `render` normally does. Note that an `_` option _must_ be specified.
11360 * This is the default value to use if you haven't specified a value for
11361 * the data type requested by DataTables.
11362 * * `function` - the function given will be executed whenever DataTables
11363 * needs to set or get the data for a cell in the column. The function
11364 * takes three parameters:
11365 * * Parameters:
11366 * * {array|object} The data source for the row (based on `data`)
11367 * * {string} The type call data requested - this will be 'filter',
11368 * 'display', 'type' or 'sort'.
11369 * * {array|object} The full data source for the row (not based on
11370 * `data`)
11371 * * Return:
11372 * * The return value from the function is what will be used for the
11373 * data requested.
11374 */
11375 "mRender": null,
11376
11377
11378 /**
11379 * Change the cell type created for the column - either TD cells or TH cells. This
11380 * can be useful as TH cells have semantic meaning in the table body, allowing them
11381 * to act as a header for a row (you may wish to add scope='row' to the TH elements).
11382 */
11383 "sCellType": "td",
11384
11385
11386 /**
11387 * Class to give to each cell in this column.
11388 */
11389 "sClass": "",
11390
11391 /**
11392 * When DataTables calculates the column widths to assign to each column,
11393 * it finds the longest string in each column and then constructs a
11394 * temporary table and reads the widths from that. The problem with this
11395 * is that "mmm" is much wider then "iiii", but the latter is a longer
11396 * string - thus the calculation can go wrong (doing it properly and putting
11397 * it into an DOM object and measuring that is horribly(!) slow). Thus as
11398 * a "work around" we provide this option. It will append its value to the
11399 * text that is found to be the longest string for the column - i.e. padding.
11400 * Generally you shouldn't need this!
11401 */
11402 "sContentPadding": "",
11403
11404
11405 /**
11406 * Allows a default value to be given for a column's data, and will be used
11407 * whenever a null data source is encountered (this can be because `data`
11408 * is set to null, or because the data source itself is null).
11409 */
11410 "sDefaultContent": null,
11411
11412
11413 /**
11414 * This parameter is only used in DataTables' server-side processing. It can
11415 * be exceptionally useful to know what columns are being displayed on the
11416 * client side, and to map these to database fields. When defined, the names
11417 * also allow DataTables to reorder information from the server if it comes
11418 * back in an unexpected order (i.e. if you switch your columns around on the
11419 * client-side, your server-side code does not also need updating).
11420 */
11421 "sName": "",
11422
11423
11424 /**
11425 * Defines a data source type for the ordering which can be used to read
11426 * real-time information from the table (updating the internally cached
11427 * version) prior to ordering. This allows ordering to occur on user
11428 * editable elements such as form inputs.
11429 */
11430 "sSortDataType": "std",
11431
11432
11433 /**
11434 * The title of this column.
11435 */
11436 "sTitle": null,
11437
11438
11439 /**
11440 * The type allows you to specify how the data for this column will be
11441 * ordered. Four types (string, numeric, date and html (which will strip
11442 * HTML tags before ordering)) are currently available. Note that only date
11443 * formats understood by Javascript's Date() object will be accepted as type
11444 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
11445 * 'numeric', 'date' or 'html' (by default). Further types can be adding
11446 * through plug-ins.
11447 */
11448 "sType": null,
11449
11450
11451 /**
11452 * Defining the width of the column, this parameter may take any CSS value
11453 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
11454 * been given a specific width through this interface ensuring that the table
11455 * remains readable.
11456 */
11457 "sWidth": null
11458 };
11459
11460 _fnHungarianMap( DataTable.defaults.column );
11461
11462
11463
11464 /**
11465 * DataTables settings object - this holds all the information needed for a
11466 * given table, including configuration, data and current application of the
11467 * table options. DataTables does not have a single instance for each DataTable
11468 * with the settings attached to that instance, but rather instances of the
11469 * DataTable "class" are created on-the-fly as needed (typically by a
11470 * $().dataTable() call) and the settings object is then applied to that
11471 * instance.
11472 *
11473 * Note that this object is related to {@link DataTable.defaults} but this
11474 * one is the internal data store for DataTables's cache of columns. It should
11475 * NOT be manipulated outside of DataTables. Any configuration should be done
11476 * through the initialisation options.
11477 */
11478 DataTable.models.oSettings = {
11479 /**
11480 * Primary features of DataTables and their enablement state.
11481 */
11482 "oFeatures": {
11483
11484 /**
11485 * Flag to say if DataTables should automatically try to calculate the
11486 * optimum table and columns widths (true) or not (false).
11487 * Note that this parameter will be set by the initialisation routine. To
11488 * set a default use {@link DataTable.defaults}.
11489 */
11490 "bAutoWidth": null,
11491
11492 /**
11493 * Delay the creation of TR and TD elements until they are actually
11494 * needed by a driven page draw. This can give a significant speed
11495 * increase for Ajax source and Javascript source data, but makes no
11496 * difference at all for DOM and server-side processing tables.
11497 * Note that this parameter will be set by the initialisation routine. To
11498 * set a default use {@link DataTable.defaults}.
11499 */
11500 "bDeferRender": null,
11501
11502 /**
11503 * Enable filtering on the table or not. Note that if this is disabled
11504 * then there is no filtering at all on the table, including fnFilter.
11505 * To just remove the filtering input use sDom and remove the 'f' option.
11506 * Note that this parameter will be set by the initialisation routine. To
11507 * set a default use {@link DataTable.defaults}.
11508 */
11509 "bFilter": null,
11510
11511 /**
11512 * Used only for compatiblity with DT1
11513 * @deprecated
11514 */
11515 "bInfo": true,
11516
11517 /**
11518 * Used only for compatiblity with DT1
11519 * @deprecated
11520 */
11521 "bLengthChange": true,
11522
11523 /**
11524 * Pagination enabled or not. Note that if this is disabled then length
11525 * changing must also be disabled.
11526 * Note that this parameter will be set by the initialisation routine. To
11527 * set a default use {@link DataTable.defaults}.
11528 */
11529 "bPaginate": null,
11530
11531 /**
11532 * Processing indicator enable flag whenever DataTables is enacting a
11533 * user request - typically an Ajax request for server-side processing.
11534 * Note that this parameter will be set by the initialisation routine. To
11535 * set a default use {@link DataTable.defaults}.
11536 */
11537 "bProcessing": null,
11538
11539 /**
11540 * Server-side processing enabled flag - when enabled DataTables will
11541 * get all data from the server for every draw - there is no filtering,
11542 * sorting or paging done on the client-side.
11543 * Note that this parameter will be set by the initialisation routine. To
11544 * set a default use {@link DataTable.defaults}.
11545 */
11546 "bServerSide": null,
11547
11548 /**
11549 * Sorting enablement flag.
11550 * Note that this parameter will be set by the initialisation routine. To
11551 * set a default use {@link DataTable.defaults}.
11552 */
11553 "bSort": null,
11554
11555 /**
11556 * Multi-column sorting
11557 * Note that this parameter will be set by the initialisation routine. To
11558 * set a default use {@link DataTable.defaults}.
11559 */
11560 "bSortMulti": null,
11561
11562 /**
11563 * Apply a class to the columns which are being sorted to provide a
11564 * visual highlight or not. This can slow things down when enabled since
11565 * there is a lot of DOM interaction.
11566 * Note that this parameter will be set by the initialisation routine. To
11567 * set a default use {@link DataTable.defaults}.
11568 */
11569 "bSortClasses": null,
11570
11571 /**
11572 * State saving enablement flag.
11573 * Note that this parameter will be set by the initialisation routine. To
11574 * set a default use {@link DataTable.defaults}.
11575 */
11576 "bStateSave": null
11577 },
11578
11579
11580 /**
11581 * Scrolling settings for a table.
11582 */
11583 "oScroll": {
11584 /**
11585 * When the table is shorter in height than sScrollY, collapse the
11586 * table container down to the height of the table (when true).
11587 * Note that this parameter will be set by the initialisation routine. To
11588 * set a default use {@link DataTable.defaults}.
11589 */
11590 "bCollapse": null,
11591
11592 /**
11593 * Width of the scrollbar for the web-browser's platform. Calculated
11594 * during table initialisation.
11595 */
11596 "iBarWidth": 0,
11597
11598 /**
11599 * Viewport width for horizontal scrolling. Horizontal scrolling is
11600 * disabled if an empty string.
11601 * Note that this parameter will be set by the initialisation routine. To
11602 * set a default use {@link DataTable.defaults}.
11603 */
11604 "sX": null,
11605
11606 /**
11607 * Width to expand the table to when using x-scrolling. Typically you
11608 * should not need to use this.
11609 * Note that this parameter will be set by the initialisation routine. To
11610 * set a default use {@link DataTable.defaults}.
11611 * @deprecated
11612 */
11613 "sXInner": null,
11614
11615 /**
11616 * Viewport height for vertical scrolling. Vertical scrolling is disabled
11617 * if an empty string.
11618 * Note that this parameter will be set by the initialisation routine. To
11619 * set a default use {@link DataTable.defaults}.
11620 */
11621 "sY": null
11622 },
11623
11624 /**
11625 * Language information for the table.
11626 */
11627 "oLanguage": {
11628 /**
11629 * Information callback function. See
11630 * {@link DataTable.defaults.fnInfoCallback}
11631 */
11632 "fnInfoCallback": null
11633 },
11634
11635 /**
11636 * Browser support parameters
11637 */
11638 "oBrowser": {
11639 /**
11640 * Determine if the vertical scrollbar is on the right or left of the
11641 * scrolling container - needed for rtl language layout, although not
11642 * all browsers move the scrollbar (Safari).
11643 */
11644 "bScrollbarLeft": false,
11645
11646 /**
11647 * Browser scrollbar width
11648 */
11649 "barWidth": 0
11650 },
11651
11652
11653 "ajax": null,
11654
11655
11656 /**
11657 * Array referencing the nodes which are used for the features. The
11658 * parameters of this object match what is allowed by sDom - i.e.
11659 * <ul>
11660 * <li>'l' - Length changing</li>
11661 * <li>'f' - Filtering input</li>
11662 * <li>'t' - The table!</li>
11663 * <li>'i' - Information</li>
11664 * <li>'p' - Pagination</li>
11665 * <li>'r' - pRocessing</li>
11666 * </ul>
11667 */
11668 "aanFeatures": [],
11669
11670 /**
11671 * Store data information - see {@link DataTable.models.oRow} for detailed
11672 * information.
11673 */
11674 "aoData": [],
11675
11676 /**
11677 * Array of indexes which are in the current display (after filtering etc)
11678 */
11679 "aiDisplay": [],
11680
11681 /**
11682 * Array of indexes for display - no filtering
11683 */
11684 "aiDisplayMaster": [],
11685
11686 /**
11687 * Map of row ids to data indexes
11688 */
11689 "aIds": {},
11690
11691 /**
11692 * Store information about each column that is in use
11693 */
11694 "aoColumns": [],
11695
11696 /**
11697 * Store information about the table's header
11698 */
11699 "aoHeader": [],
11700
11701 /**
11702 * Store information about the table's footer
11703 */
11704 "aoFooter": [],
11705
11706 /**
11707 * Store the applied global search information in case we want to force a
11708 * research or compare the old search to a new one.
11709 * Note that this parameter will be set by the initialisation routine. To
11710 * set a default use {@link DataTable.defaults}.
11711 */
11712 "oPreviousSearch": {},
11713
11714 /**
11715 * Store for named searches
11716 */
11717 searchFixed: {},
11718
11719 /**
11720 * Store the applied search for each column - see
11721 * {@link DataTable.models.oSearch} for the format that is used for the
11722 * filtering information for each column.
11723 */
11724 "aoPreSearchCols": [],
11725
11726 /**
11727 * Sorting that is applied to the table. Note that the inner arrays are
11728 * used in the following manner:
11729 * <ul>
11730 * <li>Index 0 - column number</li>
11731 * <li>Index 1 - current sorting direction</li>
11732 * </ul>
11733 * Note that this parameter will be set by the initialisation routine. To
11734 * set a default use {@link DataTable.defaults}.
11735 */
11736 "aaSorting": null,
11737
11738 /**
11739 * Sorting that is always applied to the table (i.e. prefixed in front of
11740 * aaSorting).
11741 * Note that this parameter will be set by the initialisation routine. To
11742 * set a default use {@link DataTable.defaults}.
11743 */
11744 "aaSortingFixed": [],
11745
11746 /**
11747 * If restoring a table - we should restore its width
11748 */
11749 "sDestroyWidth": 0,
11750
11751 /**
11752 * Callback functions array for every time a row is inserted (i.e. on a draw).
11753 */
11754 "aoRowCallback": [],
11755
11756 /**
11757 * Callback functions for the header on each draw.
11758 */
11759 "aoHeaderCallback": [],
11760
11761 /**
11762 * Callback function for the footer on each draw.
11763 */
11764 "aoFooterCallback": [],
11765
11766 /**
11767 * Array of callback functions for draw callback functions
11768 */
11769 "aoDrawCallback": [],
11770
11771 /**
11772 * Array of callback functions for row created function
11773 */
11774 "aoRowCreatedCallback": [],
11775
11776 /**
11777 * Callback functions for just before the table is redrawn. A return of
11778 * false will be used to cancel the draw.
11779 */
11780 "aoPreDrawCallback": [],
11781
11782 /**
11783 * Callback functions for when the table has been initialised.
11784 */
11785 "aoInitComplete": [],
11786
11787
11788 /**
11789 * Callbacks for modifying the settings to be stored for state saving, prior to
11790 * saving state.
11791 */
11792 "aoStateSaveParams": [],
11793
11794 /**
11795 * Callbacks for modifying the settings that have been stored for state saving
11796 * prior to using the stored values to restore the state.
11797 */
11798 "aoStateLoadParams": [],
11799
11800 /**
11801 * Callbacks for operating on the settings object once the saved state has been
11802 * loaded
11803 */
11804 "aoStateLoaded": [],
11805
11806 /**
11807 * Cache the table ID for quick access
11808 */
11809 "sTableId": "",
11810
11811 /**
11812 * The TABLE node for the main table
11813 */
11814 "nTable": null,
11815
11816 /**
11817 * Permanent ref to the thead element
11818 */
11819 "nTHead": null,
11820
11821 /**
11822 * Permanent ref to the tfoot element - if it exists
11823 */
11824 "nTFoot": null,
11825
11826 /**
11827 * Permanent ref to the tbody element
11828 */
11829 "nTBody": null,
11830
11831 /**
11832 * Cache the wrapper node (contains all DataTables controlled elements)
11833 */
11834 "nTableWrapper": null,
11835
11836 /**
11837 * Indicate if all required information has been read in
11838 */
11839 "bInitialised": false,
11840
11841 /**
11842 * Information about open rows. Each object in the array has the parameters
11843 * 'nTr' and 'nParent'
11844 */
11845 "aoOpenRows": [],
11846
11847 /**
11848 * Dictate the positioning of DataTables' control elements - see
11849 * {@link DataTable.model.oInit.sDom}.
11850 * Note that this parameter will be set by the initialisation routine. To
11851 * set a default use {@link DataTable.defaults}.
11852 */
11853 "sDom": null,
11854
11855 /**
11856 * Search delay (in mS)
11857 */
11858 "searchDelay": null,
11859
11860 /**
11861 * Which type of pagination should be used.
11862 * Note that this parameter will be set by the initialisation routine. To
11863 * set a default use {@link DataTable.defaults}.
11864 */
11865 "sPaginationType": "two_button",
11866
11867 /**
11868 * Number of paging controls on the page. Only used for backwards compatibility
11869 */
11870 pagingControls: 0,
11871
11872 /**
11873 * The state duration (for `stateSave`) in seconds.
11874 * Note that this parameter will be set by the initialisation routine. To
11875 * set a default use {@link DataTable.defaults}.
11876 */
11877 "iStateDuration": 0,
11878
11879 /**
11880 * Array of callback functions for state saving. Each array element is an
11881 * object with the following parameters:
11882 * <ul>
11883 * <li>function:fn - function to call. Takes two parameters, oSettings
11884 * and the JSON string to save that has been thus far created. Returns
11885 * a JSON string to be inserted into a json object
11886 * (i.e. '"param": [ 0, 1, 2]')</li>
11887 * <li>string:sName - name of callback</li>
11888 * </ul>
11889 */
11890 "aoStateSave": [],
11891
11892 /**
11893 * Array of callback functions for state loading. Each array element is an
11894 * object with the following parameters:
11895 * <ul>
11896 * <li>function:fn - function to call. Takes two parameters, oSettings
11897 * and the object stored. May return false to cancel state loading</li>
11898 * <li>string:sName - name of callback</li>
11899 * </ul>
11900 */
11901 "aoStateLoad": [],
11902
11903 /**
11904 * State that was saved. Useful for back reference
11905 */
11906 "oSavedState": null,
11907
11908 /**
11909 * State that was loaded. Useful for back reference
11910 */
11911 "oLoadedState": null,
11912
11913 /**
11914 * Note if draw should be blocked while getting data
11915 */
11916 "bAjaxDataGet": true,
11917
11918 /**
11919 * The last jQuery XHR object that was used for server-side data gathering.
11920 * This can be used for working with the XHR information in one of the
11921 * callbacks
11922 */
11923 "jqXHR": null,
11924
11925 /**
11926 * JSON returned from the server in the last Ajax request
11927 */
11928 "json": undefined,
11929
11930 /**
11931 * Data submitted as part of the last Ajax request
11932 */
11933 "oAjaxData": undefined,
11934
11935 /**
11936 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
11937 * required).
11938 * Note that this parameter will be set by the initialisation routine. To
11939 * set a default use {@link DataTable.defaults}.
11940 */
11941 "sServerMethod": null,
11942
11943 /**
11944 * Format numbers for display.
11945 * Note that this parameter will be set by the initialisation routine. To
11946 * set a default use {@link DataTable.defaults}.
11947 */
11948 "fnFormatNumber": null,
11949
11950 /**
11951 * List of options that can be used for the user selectable length menu.
11952 * Note that this parameter will be set by the initialisation routine. To
11953 * set a default use {@link DataTable.defaults}.
11954 */
11955 "aLengthMenu": null,
11956
11957 /**
11958 * Counter for the draws that the table does. Also used as a tracker for
11959 * server-side processing
11960 */
11961 "iDraw": 0,
11962
11963 /**
11964 * Indicate if a redraw is being done - useful for Ajax
11965 */
11966 "bDrawing": false,
11967
11968 /**
11969 * Draw index (iDraw) of the last error when parsing the returned data
11970 */
11971 "iDrawError": -1,
11972
11973 /**
11974 * Paging display length
11975 */
11976 "_iDisplayLength": 10,
11977
11978 /**
11979 * Paging start point - aiDisplay index
11980 */
11981 "_iDisplayStart": 0,
11982
11983 /**
11984 * Server-side processing - number of records in the result set
11985 * (i.e. before filtering), Use fnRecordsTotal rather than
11986 * this property to get the value of the number of records, regardless of
11987 * the server-side processing setting.
11988 */
11989 "_iRecordsTotal": 0,
11990
11991 /**
11992 * Server-side processing - number of records in the current display set
11993 * (i.e. after filtering). Use fnRecordsDisplay rather than
11994 * this property to get the value of the number of records, regardless of
11995 * the server-side processing setting.
11996 */
11997 "_iRecordsDisplay": 0,
11998
11999 /**
12000 * The classes to use for the table
12001 */
12002 "oClasses": {},
12003
12004 /**
12005 * Flag attached to the settings object so you can check in the draw
12006 * callback if filtering has been done in the draw. Deprecated in favour of
12007 * events.
12008 * @deprecated
12009 */
12010 "bFiltered": false,
12011
12012 /**
12013 * Flag attached to the settings object so you can check in the draw
12014 * callback if sorting has been done in the draw. Deprecated in favour of
12015 * events.
12016 * @deprecated
12017 */
12018 "bSorted": false,
12019
12020 /**
12021 * Indicate that if multiple rows are in the header and there is more than
12022 * one unique cell per column, if the top one (true) or bottom one (false)
12023 * should be used for sorting / title by DataTables.
12024 * Note that this parameter will be set by the initialisation routine. To
12025 * set a default use {@link DataTable.defaults}.
12026 */
12027 "bSortCellsTop": null,
12028
12029 /**
12030 * Initialisation object that is used for the table
12031 */
12032 "oInit": null,
12033
12034 /**
12035 * Destroy callback functions - for plug-ins to attach themselves to the
12036 * destroy so they can clean up markup and events.
12037 */
12038 "aoDestroyCallback": [],
12039
12040
12041 /**
12042 * Get the number of records in the current record set, before filtering
12043 */
12044 "fnRecordsTotal": function ()
12045 {
12046 return _fnDataSource( this ) == 'ssp' ?
12047 this._iRecordsTotal * 1 :
12048 this.aiDisplayMaster.length;
12049 },
12050
12051 /**
12052 * Get the number of records in the current record set, after filtering
12053 */
12054 "fnRecordsDisplay": function ()
12055 {
12056 return _fnDataSource( this ) == 'ssp' ?
12057 this._iRecordsDisplay * 1 :
12058 this.aiDisplay.length;
12059 },
12060
12061 /**
12062 * Get the display end point - aiDisplay index
12063 */
12064 "fnDisplayEnd": function ()
12065 {
12066 var
12067 len = this._iDisplayLength,
12068 start = this._iDisplayStart,
12069 calc = start + len,
12070 records = this.aiDisplay.length,
12071 features = this.oFeatures,
12072 paginate = features.bPaginate;
12073
12074 if ( features.bServerSide ) {
12075 return paginate === false || len === -1 ?
12076 start + records :
12077 Math.min( start+len, this._iRecordsDisplay );
12078 }
12079 else {
12080 return ! paginate || calc>records || len===-1 ?
12081 records :
12082 calc;
12083 }
12084 },
12085
12086 /**
12087 * The DataTables object for this table
12088 */
12089 "oInstance": null,
12090
12091 /**
12092 * Unique identifier for each instance of the DataTables object. If there
12093 * is an ID on the table node, then it takes that value, otherwise an
12094 * incrementing internal counter is used.
12095 */
12096 "sInstance": null,
12097
12098 /**
12099 * tabindex attribute value that is added to DataTables control elements, allowing
12100 * keyboard navigation of the table and its controls.
12101 */
12102 "iTabIndex": 0,
12103
12104 /**
12105 * DIV container for the footer scrolling table if scrolling
12106 */
12107 "nScrollHead": null,
12108
12109 /**
12110 * DIV container for the footer scrolling table if scrolling
12111 */
12112 "nScrollFoot": null,
12113
12114 /**
12115 * Last applied sort
12116 */
12117 "aLastSort": [],
12118
12119 /**
12120 * Stored plug-in instances
12121 */
12122 "oPlugins": {},
12123
12124 /**
12125 * Function used to get a row's id from the row's data
12126 */
12127 "rowIdFn": null,
12128
12129 /**
12130 * Data location where to store a row's id
12131 */
12132 "rowId": null,
12133
12134 caption: '',
12135
12136 captionNode: null,
12137
12138 colgroup: null,
12139
12140 /** Delay loading of data */
12141 deferLoading: null,
12142
12143 /** Allow auto type detection */
12144 typeDetect: true,
12145
12146 /** ResizeObserver for the container div */
12147 resizeObserver: null,
12148
12149 /** Keep a record of the last size of the container, so we can skip duplicates */
12150 containerWidth: -1
12151 };
12152
12153 /**
12154 * Extension object for DataTables that is used to provide all extension
12155 * options.
12156 *
12157 * Note that the `DataTable.ext` object is available through
12158 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
12159 * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
12160 * @namespace
12161 * @extends DataTable.models.ext
12162 */
12163
12164
12165 var extPagination = DataTable.ext.pager;
12166
12167 // Paging buttons configuration
12168 $.extend( extPagination, {
12169 simple: function () {
12170 return [ 'previous', 'next' ];
12171 },
12172
12173 full: function () {
12174 return [ 'first', 'previous', 'next', 'last' ];
12175 },
12176
12177 numbers: function () {
12178 return [ 'numbers' ];
12179 },
12180
12181 simple_numbers: function () {
12182 return [ 'previous', 'numbers', 'next' ];
12183 },
12184
12185 full_numbers: function () {
12186 return [ 'first', 'previous', 'numbers', 'next', 'last' ];
12187 },
12188
12189 first_last: function () {
12190 return ['first', 'last'];
12191 },
12192
12193 first_last_numbers: function () {
12194 return ['first', 'numbers', 'last'];
12195 },
12196
12197 // For testing and plug-ins to use
12198 _numbers: _pagingNumbers,
12199
12200 // Number of number buttons - legacy, use `numbers` option for paging feature
12201 numbers_length: 7
12202 } );
12203
12204
12205 $.extend( true, DataTable.ext.renderer, {
12206 pagingButton: {
12207 _: function (settings, buttonType, content, active, disabled) {
12208 var classes = settings.oClasses.paging;
12209 var btnClasses = [classes.button];
12210 var btn;
12211
12212 if (active) {
12213 btnClasses.push(classes.active);
12214 }
12215
12216 if (disabled) {
12217 btnClasses.push(classes.disabled)
12218 }
12219
12220 if (buttonType === 'ellipsis') {
12221 btn = $('<span class="ellipsis"></span>').html(content)[0];
12222 }
12223 else {
12224 btn = $('<button>', {
12225 class: btnClasses.join(' '),
12226 role: 'link',
12227 type: 'button'
12228 }).html(content);
12229 }
12230
12231 return {
12232 display: btn,
12233 clicker: btn
12234 }
12235 }
12236 },
12237
12238 pagingContainer: {
12239 _: function (settings, buttons) {
12240 // No wrapping element - just append directly to the host
12241 return buttons;
12242 }
12243 }
12244 } );
12245
12246 // Common function to remove new lines, strip HTML and diacritic control
12247 var _filterString = function (stripHtml, normalize) {
12248 return function (str) {
12249 if (_empty(str) || typeof str !== 'string') {
12250 return str;
12251 }
12252
12253 str = str.replace( _re_new_lines, " " );
12254
12255 if (stripHtml) {
12256 str = _stripHtml(str);
12257 }
12258
12259 if (normalize) {
12260 str = _normalize(str, false);
12261 }
12262
12263 return str;
12264 };
12265 }
12266
12267 /*
12268 * Public helper functions. These aren't used internally by DataTables, or
12269 * called by any of the options passed into DataTables, but they can be used
12270 * externally by developers working with DataTables. They are helper functions
12271 * to make working with DataTables a little bit easier.
12272 */
12273
12274 /**
12275 * Common logic for moment, luxon or a date action.
12276 *
12277 * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again
12278 */
12279 function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) {
12280 if (__moment) {
12281 return dtLib[momentFn]( arg1 );
12282 }
12283 else if (__luxon) {
12284 return dtLib[luxonFn]( arg1 );
12285 }
12286
12287 return dateFn ? dtLib[dateFn]( arg1 ) : dtLib;
12288 }
12289
12290
12291 var __mlWarning = false;
12292 var __luxon; // Can be assigned in DateTable.use()
12293 var __moment; // Can be assigned in DateTable.use()
12294
12295 /**
12296 *
12297 */
12298 function resolveWindowLibs() {
12299 if (window.luxon && ! __luxon) {
12300 __luxon = window.luxon;
12301 }
12302
12303 if (window.moment && ! __moment) {
12304 __moment = window.moment;
12305 }
12306 }
12307
12308 function __mldObj (d, format, locale) {
12309 var dt;
12310
12311 resolveWindowLibs();
12312
12313 if (__moment) {
12314 dt = __moment.utc( d, format, locale, true );
12315
12316 if (! dt.isValid()) {
12317 return null;
12318 }
12319 }
12320 else if (__luxon) {
12321 dt = format && typeof d === 'string'
12322 ? __luxon.DateTime.fromFormat( d, format )
12323 : __luxon.DateTime.fromISO( d );
12324
12325 if (! dt.isValid) {
12326 return null;
12327 }
12328
12329 dt = dt.setLocale(locale);
12330 }
12331 else if (! format) {
12332 // No format given, must be ISO
12333 dt = new Date(d);
12334 }
12335 else {
12336 if (! __mlWarning) {
12337 alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17');
12338 }
12339
12340 __mlWarning = true;
12341 }
12342
12343 return dt;
12344 }
12345
12346 // Wrapper for date, datetime and time which all operate the same way with the exception of
12347 // the output string for auto locale support
12348 function __mlHelper (localeString) {
12349 return function ( from, to, locale, def ) {
12350 // Luxon and Moment support
12351 // Argument shifting
12352 if ( arguments.length === 0 ) {
12353 locale = 'en';
12354 to = null; // means toLocaleString
12355 from = null; // means iso8601
12356 }
12357 else if ( arguments.length === 1 ) {
12358 locale = 'en';
12359 to = from;
12360 from = null;
12361 }
12362 else if ( arguments.length === 2 ) {
12363 locale = to;
12364 to = from;
12365 from = null;
12366 }
12367
12368 var typeName = 'datetime' + (to ? '-' + to : '');
12369
12370 // Add type detection and sorting specific to this date format - we need to be able to identify
12371 // date type columns as such, rather than as numbers in extensions. Hence the need for this.
12372 if (! DataTable.ext.type.order[typeName + '-pre']) {
12373 DataTable.type(typeName, {
12374 detect: function (d) {
12375 // The renderer will give the value to type detect as the type!
12376 return d === typeName ? typeName : false;
12377 },
12378 order: {
12379 pre: function (d) {
12380 // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a
12381 // `valueOf` which gives milliseconds epoch
12382 return d.valueOf();
12383 }
12384 },
12385 className: 'dt-right'
12386 });
12387 }
12388
12389 return function ( d, type ) {
12390 // Allow for a default value
12391 if (d === null || d === undefined) {
12392 if (def === '--now') {
12393 // We treat everything as UTC further down, so no changes are
12394 // made, as such need to get the local date / time as if it were
12395 // UTC
12396 var local = new Date();
12397 d = new Date( Date.UTC(
12398 local.getFullYear(), local.getMonth(), local.getDate(),
12399 local.getHours(), local.getMinutes(), local.getSeconds()
12400 ) );
12401 }
12402 else {
12403 d = '';
12404 }
12405 }
12406
12407 if (type === 'type') {
12408 // Typing uses the type name for fast matching
12409 return typeName;
12410 }
12411
12412 if (d === '') {
12413 return type !== 'sort'
12414 ? ''
12415 : __mldObj('0000-01-01 00:00:00', null, locale);
12416 }
12417
12418 // Shortcut. If `from` and `to` are the same, we are using the renderer to
12419 // format for ordering, not display - its already in the display format.
12420 if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) {
12421 return d;
12422 }
12423
12424 var dt = __mldObj(d, from, locale);
12425
12426 if (dt === null) {
12427 return d;
12428 }
12429
12430 if (type === 'sort') {
12431 return dt;
12432 }
12433
12434 var formatted = to === null
12435 ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]()
12436 : __mld(dt, 'format', 'toFormat', 'toISOString', to);
12437
12438 // XSS protection
12439 return type === 'display' ?
12440 _escapeHtml( formatted ) :
12441 formatted;
12442 };
12443 }
12444 }
12445
12446 // Based on locale, determine standard number formatting
12447 // Fallback for legacy browsers is US English
12448 var __thousands = ',';
12449 var __decimal = '.';
12450
12451 if (window.Intl !== undefined) {
12452 try {
12453 var num = new Intl.NumberFormat().formatToParts(100000.1);
12454
12455 for (var i=0 ; i<num.length ; i++) {
12456 if (num[i].type === 'group') {
12457 __thousands = num[i].value;
12458 }
12459 else if (num[i].type === 'decimal') {
12460 __decimal = num[i].value;
12461 }
12462 }
12463 }
12464 catch (e) {
12465 // noop
12466 }
12467 }
12468
12469 // Formatted date time detection - use by declaring the formats you are going to use
12470 DataTable.datetime = function ( format, locale ) {
12471 var typeName = 'datetime-' + format;
12472
12473 if (! locale) {
12474 locale = 'en';
12475 }
12476
12477 if (! DataTable.ext.type.order[typeName]) {
12478 DataTable.type(typeName, {
12479 detect: function (d) {
12480 var dt = __mldObj(d, format, locale);
12481 return d === '' || dt ? typeName : false;
12482 },
12483 order: {
12484 pre: function (d) {
12485 return __mldObj(d, format, locale) || 0;
12486 }
12487 },
12488 className: 'dt-right'
12489 });
12490 }
12491 }
12492
12493 /**
12494 * Helpers for `columns.render`.
12495 *
12496 * The options defined here can be used with the `columns.render` initialisation
12497 * option to provide a display renderer. The following functions are defined:
12498 *
12499 * * `moment` - Uses the MomentJS library to convert from a given format into another.
12500 * This renderer has three overloads:
12501 * * 1 parameter:
12502 * * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`)
12503 * * 2 parameters:
12504 * * `string` - Format to convert from
12505 * * `string` - Format to convert to. Assumes `en` locale
12506 * * 3 parameters:
12507 * * `string` - Format to convert from
12508 * * `string` - Format to convert to
12509 * * `string` - Locale
12510 * * `number` - Will format numeric data (defined by `columns.data`) for
12511 * display, retaining the original unformatted data for sorting and filtering.
12512 * It takes 5 parameters:
12513 * * `string` - Thousands grouping separator
12514 * * `string` - Decimal point indicator
12515 * * `integer` - Number of decimal points to show
12516 * * `string` (optional) - Prefix.
12517 * * `string` (optional) - Postfix (/suffix).
12518 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
12519 * parameters.
12520 *
12521 * @example
12522 * // Column definition using the number renderer
12523 * {
12524 * data: "salary",
12525 * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
12526 * }
12527 *
12528 * @namespace
12529 */
12530 DataTable.render = {
12531 date: __mlHelper('toLocaleDateString'),
12532 datetime: __mlHelper('toLocaleString'),
12533 time: __mlHelper('toLocaleTimeString'),
12534 number: function ( thousands, decimal, precision, prefix, postfix ) {
12535 // Auto locale detection
12536 if (thousands === null || thousands === undefined) {
12537 thousands = __thousands;
12538 }
12539
12540 if (decimal === null || decimal === undefined) {
12541 decimal = __decimal;
12542 }
12543
12544 return {
12545 display: function ( d ) {
12546 if ( typeof d !== 'number' && typeof d !== 'string' ) {
12547 return d;
12548 }
12549
12550 if (d === '' || d === null) {
12551 return d;
12552 }
12553
12554 var negative = d < 0 ? '-' : '';
12555 var flo = parseFloat( d );
12556 var abs = Math.abs(flo);
12557
12558 // Scientific notation for large and small numbers
12559 if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0) ) {
12560 var exp = flo.toExponential(precision).split(/e\+?/);
12561 return exp[0] + ' x 10<sup>' + exp[1] + '</sup>';
12562 }
12563
12564 // If NaN then there isn't much formatting that we can do - just
12565 // return immediately, escaping any HTML (this was supposed to
12566 // be a number after all)
12567 if ( isNaN( flo ) ) {
12568 return _escapeHtml( d );
12569 }
12570
12571 flo = flo.toFixed( precision );
12572 d = Math.abs( flo );
12573
12574 var intPart = parseInt( d, 10 );
12575 var floatPart = precision ?
12576 decimal+(d - intPart).toFixed( precision ).substring( 2 ):
12577 '';
12578
12579 // If zero, then can't have a negative prefix
12580 if (intPart === 0 && parseFloat(floatPart) === 0) {
12581 negative = '';
12582 }
12583
12584 return negative + (prefix||'') +
12585 intPart.toString().replace(
12586 /\B(?=(\d{3})+(?!\d))/g, thousands
12587 ) +
12588 floatPart +
12589 (postfix||'');
12590 }
12591 };
12592 },
12593
12594 text: function () {
12595 return {
12596 display: _escapeHtml,
12597 filter: _escapeHtml
12598 };
12599 }
12600 };
12601
12602
12603 var _extTypes = DataTable.ext.type;
12604
12605 // Get / set type
12606 DataTable.type = function (name, prop, val) {
12607 if (! prop) {
12608 return {
12609 className: _extTypes.className[name],
12610 detect: _extTypes.detect.find(function (fn) {
12611 return fn._name === name;
12612 }),
12613 order: {
12614 pre: _extTypes.order[name + '-pre'],
12615 asc: _extTypes.order[name + '-asc'],
12616 desc: _extTypes.order[name + '-desc']
12617 },
12618 render: _extTypes.render[name],
12619 search: _extTypes.search[name]
12620 };
12621 }
12622
12623 var setProp = function(prop, propVal) {
12624 _extTypes[prop][name] = propVal;
12625 };
12626 var setDetect = function (detect) {
12627 // `detect` can be a function or an object - we set a name
12628 // property for either - that is used for the detection
12629 Object.defineProperty(detect, "_name", {value: name});
12630
12631 var idx = _extTypes.detect.findIndex(function (item) {
12632 return item._name === name;
12633 });
12634
12635 if (idx === -1) {
12636 _extTypes.detect.unshift(detect);
12637 }
12638 else {
12639 _extTypes.detect.splice(idx, 1, detect);
12640 }
12641 };
12642 var setOrder = function (obj) {
12643 _extTypes.order[name + '-pre'] = obj.pre; // can be undefined
12644 _extTypes.order[name + '-asc'] = obj.asc; // can be undefined
12645 _extTypes.order[name + '-desc'] = obj.desc; // can be undefined
12646 };
12647
12648 // prop is optional
12649 if (val === undefined) {
12650 val = prop;
12651 prop = null;
12652 }
12653
12654 if (prop === 'className') {
12655 setProp('className', val);
12656 }
12657 else if (prop === 'detect') {
12658 setDetect(val);
12659 }
12660 else if (prop === 'order') {
12661 setOrder(val);
12662 }
12663 else if (prop === 'render') {
12664 setProp('render', val);
12665 }
12666 else if (prop === 'search') {
12667 setProp('search', val);
12668 }
12669 else if (! prop) {
12670 if (val.className) {
12671 setProp('className', val.className);
12672 }
12673
12674 if (val.detect !== undefined) {
12675 setDetect(val.detect);
12676 }
12677
12678 if (val.order) {
12679 setOrder(val.order);
12680 }
12681
12682 if (val.render !== undefined) {
12683 setProp('render', val.render);
12684 }
12685
12686 if (val.search !== undefined) {
12687 setProp('search', val.search);
12688 }
12689 }
12690 }
12691
12692 // Get a list of types
12693 DataTable.types = function () {
12694 return _extTypes.detect.map(function (fn) {
12695 return fn._name;
12696 });
12697 };
12698
12699 var __diacriticSort = function (a, b) {
12700 a = a !== null && a !== undefined ? a.toString().toLowerCase() : '';
12701 b = b !== null && b !== undefined ? b.toString().toLowerCase() : '';
12702
12703 // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old
12704 // Safari and thus can disable this check
12705 // eslint-disable-next-line compat/compat
12706 return a.localeCompare(b, navigator.languages[0] || navigator.language, {
12707 numeric: true,
12708 ignorePunctuation: true,
12709 });
12710 }
12711
12712 var __diacriticHtmlSort = function (a, b) {
12713 a = _stripHtml(a);
12714 b = _stripHtml(b);
12715
12716 return __diacriticSort(a, b);
12717 }
12718
12719 //
12720 // Built in data types
12721 //
12722
12723 DataTable.type('string', {
12724 detect: function () {
12725 return 'string';
12726 },
12727 order: {
12728 pre: function ( a ) {
12729 // This is a little complex, but faster than always calling toString,
12730 // http://jsperf.com/tostring-v-check
12731 return _empty(a) && typeof a !== 'boolean' ?
12732 '' :
12733 typeof a === 'string' ?
12734 a.toLowerCase() :
12735 ! a.toString ?
12736 '' :
12737 a.toString();
12738 }
12739 },
12740 search: _filterString(false, true)
12741 });
12742
12743 DataTable.type('string-utf8', {
12744 detect: {
12745 allOf: function ( d ) {
12746 return true;
12747 },
12748 oneOf: function ( d ) {
12749 // At least one data point must contain a non-ASCII character
12750 // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-)
12751 // this data type won't be supported.
12752 // eslint-disable-next-line compat/compat
12753 return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/);
12754 }
12755 },
12756 order: {
12757 asc: __diacriticSort,
12758 desc: function (a, b) {
12759 return __diacriticSort(a, b) * -1;
12760 }
12761 },
12762 search: _filterString(false, true)
12763 });
12764
12765
12766 DataTable.type('html', {
12767 detect: {
12768 allOf: function ( d ) {
12769 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
12770 },
12771 oneOf: function ( d ) {
12772 // At least one data point must contain a `<`
12773 return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1;
12774 }
12775 },
12776 order: {
12777 pre: function ( a ) {
12778 return _empty(a) ?
12779 '' :
12780 a.replace ?
12781 _stripHtml(a).trim().toLowerCase() :
12782 a+'';
12783 }
12784 },
12785 search: _filterString(true, true)
12786 });
12787
12788
12789 DataTable.type('html-utf8', {
12790 detect: {
12791 allOf: function ( d ) {
12792 return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
12793 },
12794 oneOf: function ( d ) {
12795 // At least one data point must contain a `<` and a non-ASCII character
12796 // eslint-disable-next-line compat/compat
12797 return navigator.languages &&
12798 ! _empty( d ) &&
12799 typeof d === 'string' &&
12800 d.indexOf('<') !== -1 &&
12801 typeof d === 'string' && d.match(/[^\x00-\x7F]/);
12802 }
12803 },
12804 order: {
12805 asc: __diacriticHtmlSort,
12806 desc: function (a, b) {
12807 return __diacriticHtmlSort(a, b) * -1;
12808 }
12809 },
12810 search: _filterString(true, true)
12811 });
12812
12813
12814 DataTable.type('date', {
12815 className: 'dt-type-date',
12816 detect: {
12817 allOf: function ( d ) {
12818 // V8 tries _very_ hard to make a string passed into `Date.parse()`
12819 // valid, so we need to use a regex to restrict date formats. Use a
12820 // plug-in for anything other than ISO8601 style strings
12821 if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
12822 return null;
12823 }
12824 var parsed = Date.parse(d);
12825 return (parsed !== null && !isNaN(parsed)) || _empty(d);
12826 },
12827 oneOf: function ( d ) {
12828 // At least one entry must be a date or a string with a date
12829 return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d));
12830 }
12831 },
12832 order: {
12833 pre: function ( d ) {
12834 var ts = Date.parse( d );
12835 return isNaN(ts) ? -Infinity : ts;
12836 }
12837 }
12838 });
12839
12840
12841 DataTable.type('html-num-fmt', {
12842 className: 'dt-type-numeric',
12843 detect: {
12844 allOf: function ( d, settings ) {
12845 var decimal = settings.oLanguage.sDecimal;
12846 return _htmlNumeric( d, decimal, true, false );
12847 },
12848 oneOf: function (d, settings) {
12849 // At least one data point must contain a numeric value
12850 var decimal = settings.oLanguage.sDecimal;
12851 return _htmlNumeric( d, decimal, true, false );
12852 }
12853 },
12854 order: {
12855 pre: function ( d, s ) {
12856 var dp = s.oLanguage.sDecimal;
12857 return __numericReplace( d, dp, _re_html, _re_formatted_numeric );
12858 }
12859 },
12860 search: _filterString(true, true)
12861 });
12862
12863
12864 DataTable.type('html-num', {
12865 className: 'dt-type-numeric',
12866 detect: {
12867 allOf: function ( d, settings ) {
12868 var decimal = settings.oLanguage.sDecimal;
12869 return _htmlNumeric( d, decimal, false, true );
12870 },
12871 oneOf: function (d, settings) {
12872 // At least one data point must contain a numeric value
12873 var decimal = settings.oLanguage.sDecimal;
12874 return _htmlNumeric( d, decimal, false, false );
12875 }
12876 },
12877 order: {
12878 pre: function ( d, s ) {
12879 var dp = s.oLanguage.sDecimal;
12880 return __numericReplace( d, dp, _re_html );
12881 }
12882 },
12883 search: _filterString(true, true)
12884 });
12885
12886
12887 DataTable.type('num-fmt', {
12888 className: 'dt-type-numeric',
12889 detect: {
12890 allOf: function ( d, settings ) {
12891 var decimal = settings.oLanguage.sDecimal;
12892 return _isNumber( d, decimal, true, true );
12893 },
12894 oneOf: function (d, settings) {
12895 // At least one data point must contain a numeric value
12896 var decimal = settings.oLanguage.sDecimal;
12897 return _isNumber( d, decimal, true, false );
12898 }
12899 },
12900 order: {
12901 pre: function ( d, s ) {
12902 var dp = s.oLanguage.sDecimal;
12903 return __numericReplace( d, dp, _re_formatted_numeric );
12904 }
12905 }
12906 });
12907
12908
12909 DataTable.type('num', {
12910 className: 'dt-type-numeric',
12911 detect: {
12912 allOf: function ( d, settings ) {
12913 var decimal = settings.oLanguage.sDecimal;
12914 return _isNumber( d, decimal, false, true );
12915 },
12916 oneOf: function (d, settings) {
12917 // At least one data point must contain a numeric value
12918 var decimal = settings.oLanguage.sDecimal;
12919 return _isNumber( d, decimal, false, false );
12920 }
12921 },
12922 order: {
12923 pre: function (d, s) {
12924 var dp = s.oLanguage.sDecimal;
12925 return __numericReplace( d, dp );
12926 }
12927 }
12928 });
12929
12930
12931
12932
12933 var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
12934 if ( d !== 0 && (!d || d === '-') ) {
12935 return -Infinity;
12936 }
12937
12938 var type = typeof d;
12939
12940 if (type === 'number' || type === 'bigint') {
12941 return d;
12942 }
12943
12944 // If a decimal place other than `.` is used, it needs to be given to the
12945 // function so we can detect it and replace with a `.` which is the only
12946 // decimal place Javascript recognises - it is not locale aware.
12947 if ( decimalPlace ) {
12948 d = _numToDecimal( d, decimalPlace );
12949 }
12950
12951 if ( d.replace ) {
12952 if ( re1 ) {
12953 d = d.replace( re1, '' );
12954 }
12955
12956 if ( re2 ) {
12957 d = d.replace( re2, '' );
12958 }
12959 }
12960
12961 return d * 1;
12962 };
12963
12964
12965 $.extend( true, DataTable.ext.renderer, {
12966 footer: {
12967 _: function ( settings, cell, classes ) {
12968 cell.addClass(classes.tfoot.cell);
12969 }
12970 },
12971
12972 header: {
12973 _: function ( settings, cell, classes ) {
12974 cell.addClass(classes.thead.cell);
12975
12976 if (! settings.oFeatures.bSort) {
12977 cell.addClass(classes.order.none);
12978 }
12979
12980 var legacyTop = settings.bSortCellsTop;
12981 var headerRows = cell.closest('thead').find('tr');
12982 var rowIdx = cell.parent().index();
12983
12984 // Conditions to not apply the ordering icons
12985 if (
12986 // Cells and rows which have the attribute to disable the icons
12987 cell.attr('data-dt-order') === 'disable' ||
12988 cell.parent().attr('data-dt-order') === 'disable' ||
12989
12990 // Legacy support for `orderCellsTop`. If it is set, then cells
12991 // which are not in the top or bottom row of the header (depending
12992 // on the value) do not get the sorting classes applied to them
12993 (legacyTop === true && rowIdx !== 0) ||
12994 (legacyTop === false && rowIdx !== headerRows.length - 1)
12995 ) {
12996 return;
12997 }
12998
12999 // No additional mark-up required
13000 // Attach a sort listener to update on sort - note that using the
13001 // `DT` namespace will allow the event to be removed automatically
13002 // on destroy, while the `dt` namespaced event is the one we are
13003 // listening for
13004 $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) {
13005 if ( settings !== ctx ) { // need to check this this is the host
13006 return; // table, not a nested one
13007 }
13008
13009 var sorting = ctx.sortDetails;
13010
13011 if (! sorting) {
13012 return;
13013 }
13014
13015 var i;
13016 var orderClasses = classes.order;
13017 var columns = ctx.api.columns( cell );
13018 var col = settings.aoColumns[columns.flatten()[0]];
13019 var orderable = columns.orderable().includes(true);
13020 var ariaType = '';
13021 var indexes = columns.indexes();
13022 var sortDirs = columns.orderable(true).flatten();
13023 var orderedColumns = _pluck(sorting, 'col');
13024 var tabIndex = settings.iTabIndex;
13025
13026 cell
13027 .removeClass(
13028 orderClasses.isAsc +' '+
13029 orderClasses.isDesc
13030 )
13031 .toggleClass( orderClasses.none, ! orderable )
13032 .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
13033 .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
13034
13035 // Determine if all of the columns that this cell covers are included in the
13036 // current ordering
13037 var isOrdering = true;
13038
13039 for (i=0; i<indexes.length; i++) {
13040 if (! orderedColumns.includes(indexes[i])) {
13041 isOrdering = false;
13042 }
13043 }
13044
13045 if ( isOrdering ) {
13046 // Get the ordering direction for the columns under this cell
13047 // Note that it is possible for a cell to be asc and desc sorting
13048 // (column spanning cells)
13049 var orderDirs = columns.order();
13050
13051 cell.addClass(
13052 orderDirs.includes('asc') ? orderClasses.isAsc : '' +
13053 orderDirs.includes('desc') ? orderClasses.isDesc : ''
13054 );
13055 }
13056
13057 // Find the first visible column that has ordering applied to it - it get's
13058 // the aria information, as the ARIA spec says that only one column should
13059 // be marked with aria-sort
13060 var firstVis = -1; // column index
13061
13062 for (i=0; i<orderedColumns.length; i++) {
13063 if (settings.aoColumns[orderedColumns[i]].bVisible) {
13064 firstVis = orderedColumns[i];
13065 break;
13066 }
13067 }
13068
13069 if (indexes[0] == firstVis) {
13070 var firstSort = sorting[0];
13071 var sortOrder = col.asSorting;
13072
13073 cell.attr('aria-sort', firstSort.dir === 'asc' ? 'ascending' : 'descending');
13074
13075 // Determine if the next click will remove sorting or change the sort
13076 ariaType = ! sortOrder[firstSort.index + 1] ? 'Remove' : 'Reverse';
13077 }
13078 else {
13079 cell.removeAttr('aria-sort');
13080 }
13081
13082 // Make the headers tab-able for keyboard navigation
13083 if (orderable) {
13084 var orderSpan = cell.find('.dt-column-order');
13085
13086 orderSpan
13087 .attr('role', 'button')
13088 .attr('aria-label', orderable
13089 ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
13090 : col.ariaTitle
13091 );
13092
13093 if (tabIndex !== -1) {
13094 orderSpan.attr('tabindex', tabIndex);
13095 }
13096 }
13097 } );
13098 }
13099 },
13100
13101 layout: {
13102 _: function ( settings, container, items ) {
13103 var classes = settings.oClasses.layout;
13104 var row = $('<div/>')
13105 .attr('id', items.id || null)
13106 .addClass(items.className || classes.row)
13107 .appendTo( container );
13108
13109 DataTable.ext.renderer.layout._forLayoutRow(items, function (key, val) {
13110 if (key === 'id' || key === 'className') {
13111 return;
13112 }
13113
13114 var klass = '';
13115
13116 if (val.table) {
13117 row.addClass(classes.tableRow);
13118 klass += classes.tableCell + ' ';
13119 }
13120
13121 if (key === 'start') {
13122 klass += classes.start;
13123 }
13124 else if (key === 'end') {
13125 klass += classes.end;
13126 }
13127 else {
13128 klass += classes.full;
13129 }
13130
13131 $('<div/>')
13132 .attr({
13133 id: val.id || null,
13134 "class": val.className
13135 ? val.className
13136 : classes.cell + ' ' + klass
13137 })
13138 .append( val.contents )
13139 .appendTo( row );
13140 });
13141 },
13142
13143 // Shared for use by the styling frameworks
13144 _forLayoutRow: function (items, fn) {
13145 // As we are inserting dom elements, we need start / end in a
13146 // specific order, this function is used for sorting the layout
13147 // keys.
13148 var layoutEnum = function (x) {
13149 switch (x) {
13150 case '': return 0;
13151 case 'start': return 1;
13152 case 'end': return 2;
13153 default: return 3;
13154 }
13155 };
13156
13157 Object
13158 .keys(items)
13159 .sort(function (a, b) {
13160 return layoutEnum(a) - layoutEnum(b);
13161 })
13162 .forEach(function (key) {
13163 fn(key, items[key]);
13164 });
13165 }
13166 }
13167 } );
13168
13169
13170 DataTable.feature = {};
13171
13172 // Third parameter is internal only!
13173 DataTable.feature.register = function ( name, cb, legacy ) {
13174 DataTable.ext.features[ name ] = cb;
13175
13176 if (legacy) {
13177 _ext.feature.push({
13178 cFeature: legacy,
13179 fnInit: cb
13180 });
13181 }
13182 };
13183
13184 function _divProp(el, prop, val) {
13185 if (val) {
13186 el[prop] = val;
13187 }
13188 }
13189
13190 DataTable.feature.register( 'div', function ( settings, opts ) {
13191 var n = $('<div>')[0];
13192
13193 if (opts) {
13194 _divProp(n, 'className', opts.className);
13195 _divProp(n, 'id', opts.id);
13196 _divProp(n, 'innerHTML', opts.html);
13197 _divProp(n, 'textContent', opts.text);
13198 }
13199
13200 return n;
13201 } );
13202
13203 DataTable.feature.register( 'info', function ( settings, opts ) {
13204 // For compatibility with the legacy `info` top level option
13205 if (! settings.oFeatures.bInfo) {
13206 return null;
13207 }
13208
13209 var
13210 lang = settings.oLanguage,
13211 tid = settings.sTableId,
13212 n = $('<div/>', {
13213 'class': settings.oClasses.info.container,
13214 } );
13215
13216 opts = $.extend({
13217 callback: lang.fnInfoCallback,
13218 empty: lang.sInfoEmpty,
13219 postfix: lang.sInfoPostFix,
13220 search: lang.sInfoFiltered,
13221 text: lang.sInfo,
13222 }, opts);
13223
13224
13225 // Update display on each draw
13226 settings.aoDrawCallback.push(function (s) {
13227 _fnUpdateInfo(s, opts, n);
13228 });
13229
13230 // For the first info display in the table, we add a callback and aria information.
13231 if (! settings._infoEl) {
13232 n.attr({
13233 'aria-live': 'polite',
13234 id: tid+'_info',
13235 role: 'status'
13236 });
13237
13238 // Table is described by our info div
13239 $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
13240
13241 settings._infoEl = n;
13242 }
13243
13244 return n;
13245 }, 'i' );
13246
13247 /**
13248 * Update the information elements in the display
13249 * @param {object} settings dataTables settings object
13250 * @memberof DataTable#oApi
13251 */
13252 function _fnUpdateInfo ( settings, opts, node )
13253 {
13254 var
13255 start = settings._iDisplayStart+1,
13256 end = settings.fnDisplayEnd(),
13257 max = settings.fnRecordsTotal(),
13258 total = settings.fnRecordsDisplay(),
13259 out = total
13260 ? opts.text
13261 : opts.empty;
13262
13263 if ( total !== max ) {
13264 // Record set after filtering
13265 out += ' ' + opts.search;
13266 }
13267
13268 // Convert the macros
13269 out += opts.postfix;
13270 out = _fnMacros( settings, out );
13271
13272 if ( opts.callback ) {
13273 out = opts.callback.call( settings.oInstance,
13274 settings, start, end, max, total, out
13275 );
13276 }
13277
13278 node.html( out );
13279
13280 _fnCallbackFire(settings, null, 'info', [settings, node[0], out]);
13281 }
13282
13283 var __searchCounter = 0;
13284
13285 // opts
13286 // - text
13287 // - placeholder
13288 DataTable.feature.register( 'search', function ( settings, opts ) {
13289 // Don't show the input if filtering isn't available on the table
13290 if (! settings.oFeatures.bFilter) {
13291 return null;
13292 }
13293
13294 var classes = settings.oClasses.search;
13295 var tableId = settings.sTableId;
13296 var language = settings.oLanguage;
13297 var previousSearch = settings.oPreviousSearch;
13298 var input = '<input type="search" class="'+classes.input+'"/>';
13299
13300 opts = $.extend({
13301 placeholder: language.sSearchPlaceholder,
13302 processing: false,
13303 text: language.sSearch
13304 }, opts);
13305
13306 // The _INPUT_ is optional - is appended if not present
13307 if (opts.text.indexOf('_INPUT_') === -1) {
13308 opts.text += '_INPUT_';
13309 }
13310
13311 opts.text = _fnMacros(settings, opts.text);
13312
13313 // We can put the <input> outside of the label if it is at the start or end
13314 // which helps improve accessability (not all screen readers like implicit
13315 // for elements).
13316 var end = opts.text.match(/_INPUT_$/);
13317 var start = opts.text.match(/^_INPUT_/);
13318 var removed = opts.text.replace(/_INPUT_/, '');
13319 var str = '<label>' + opts.text + '</label>';
13320
13321 if (start) {
13322 str = '_INPUT_<label>' + removed + '</label>';
13323 }
13324 else if (end) {
13325 str = '<label>' + removed + '</label>_INPUT_';
13326 }
13327
13328 var filter = $('<div>')
13329 .addClass(classes.container)
13330 .append(str.replace(/_INPUT_/, input));
13331
13332 // add for and id to label and input
13333 filter.find('label').attr('for', 'dt-search-' + __searchCounter);
13334 filter.find('input').attr('id', 'dt-search-' + __searchCounter);
13335 __searchCounter++;
13336
13337 var searchFn = function(event) {
13338 var val = this.value;
13339
13340 if(previousSearch.return && event.key !== "Enter") {
13341 return;
13342 }
13343
13344 /* Now do the filter */
13345 if ( val != previousSearch.search ) {
13346 _fnProcessingRun(settings, opts.processing, function () {
13347 previousSearch.search = val;
13348
13349 _fnFilterComplete( settings, previousSearch );
13350
13351 // Need to redraw, without resorting
13352 settings._iDisplayStart = 0;
13353 _fnDraw( settings );
13354 });
13355 }
13356 };
13357
13358 var searchDelay = settings.searchDelay !== null ?
13359 settings.searchDelay :
13360 0;
13361
13362 var jqFilter = $('input', filter)
13363 .val( previousSearch.search )
13364 .attr( 'placeholder', opts.placeholder )
13365 .on(
13366 'keyup.DT search.DT input.DT paste.DT cut.DT',
13367 searchDelay ?
13368 DataTable.util.debounce( searchFn, searchDelay ) :
13369 searchFn
13370 )
13371 .on( 'mouseup.DT', function(e) {
13372 // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking
13373 // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn`
13374 // checks the value to see if it has changed. In other browsers it won't have.
13375 setTimeout( function () {
13376 searchFn.call(jqFilter[0], e);
13377 }, 10);
13378 } )
13379 .on( 'keypress.DT', function(e) {
13380 /* Prevent form submission */
13381 if ( e.keyCode == 13 ) {
13382 return false;
13383 }
13384 } )
13385 .attr('aria-controls', tableId);
13386
13387 // Update the input elements whenever the table is filtered
13388 $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
13389 if ( settings === s && jqFilter[0] !== document.activeElement ) {
13390 jqFilter.val( typeof previousSearch.search !== 'function'
13391 ? previousSearch.search
13392 : ''
13393 );
13394 }
13395 } );
13396
13397 return filter;
13398 }, 'f' );
13399
13400 // opts
13401 // - type - button configuration
13402 // - buttons - number of buttons to show - must be odd
13403 DataTable.feature.register( 'paging', function ( settings, opts ) {
13404 // Don't show the paging input if the table doesn't have paging enabled
13405 if (! settings.oFeatures.bPaginate) {
13406 return null;
13407 }
13408
13409 opts = $.extend({
13410 buttons: DataTable.ext.pager.numbers_length,
13411 type: settings.sPaginationType,
13412 boundaryNumbers: true,
13413 firstLast: true,
13414 previousNext: true,
13415 numbers: true
13416 }, opts);
13417
13418 var host = $('<div/>')
13419 .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : ''))
13420 .append(
13421 $('<nav>')
13422 .attr('aria-label', 'pagination')
13423 .addClass(settings.oClasses.paging.nav)
13424 );
13425 var draw = function () {
13426 _pagingDraw(settings, host.children(), opts);
13427 };
13428
13429 settings.aoDrawCallback.push(draw);
13430
13431 // Responsive redraw of paging control
13432 $(settings.nTable).on('column-sizing.dt.DT', draw);
13433
13434 return host;
13435 }, 'p' );
13436
13437 /**
13438 * Dynamically create the button type array based on the configuration options.
13439 * This will only happen if the paging type is not defined.
13440 */
13441 function _pagingDynamic(opts) {
13442 var out = [];
13443
13444 if (opts.numbers) {
13445 out.push('numbers');
13446 }
13447
13448 if (opts.previousNext) {
13449 out.unshift('previous');
13450 out.push('next');
13451 }
13452
13453 if (opts.firstLast) {
13454 out.unshift('first');
13455 out.push('last');
13456 }
13457
13458 return out;
13459 }
13460
13461 function _pagingDraw(settings, host, opts) {
13462 if (! settings._bInitComplete) {
13463 return;
13464 }
13465
13466 var
13467 plugin = opts.type
13468 ? DataTable.ext.pager[ opts.type ]
13469 : _pagingDynamic,
13470 aria = settings.oLanguage.oAria.paginate || {},
13471 start = settings._iDisplayStart,
13472 len = settings._iDisplayLength,
13473 visRecords = settings.fnRecordsDisplay(),
13474 all = len === -1,
13475 page = all ? 0 : Math.ceil( start / len ),
13476 pages = all ? 1 : Math.ceil( visRecords / len ),
13477 buttons = [],
13478 buttonEls = [],
13479 buttonsNested = plugin(opts)
13480 .map(function (val) {
13481 return val === 'numbers'
13482 ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
13483 : val;
13484 });
13485
13486 // .flat() would be better, but not supported in old Safari
13487 buttons = buttons.concat.apply(buttons, buttonsNested);
13488
13489 for (var i=0 ; i<buttons.length ; i++) {
13490 var button = buttons[i];
13491
13492 var btnInfo = _pagingButtonInfo(settings, button, page, pages);
13493 var btn = _fnRenderer( settings, 'pagingButton' )(
13494 settings,
13495 button,
13496 btnInfo.display,
13497 btnInfo.active,
13498 btnInfo.disabled
13499 );
13500
13501 var ariaLabel = typeof button === 'string'
13502 ? aria[ button ]
13503 : aria.number
13504 ? aria.number + (button+1)
13505 : null;
13506
13507 // Common attributes
13508 $(btn.clicker).attr({
13509 'aria-controls': settings.sTableId,
13510 'aria-disabled': btnInfo.disabled ? 'true' : null,
13511 'aria-current': btnInfo.active ? 'page' : null,
13512 'aria-label': ariaLabel,
13513 'data-dt-idx': button,
13514 'tabIndex': btnInfo.disabled
13515 ? -1
13516 : settings.iTabIndex && btn.clicker[0].nodeName.toLowerCase() !== 'span'
13517 ? settings.iTabIndex
13518 : null, // `0` doesn't need a tabIndex since it is the default
13519 });
13520
13521 if (typeof button !== 'number') {
13522 $(btn.clicker).addClass(button);
13523 }
13524
13525 _fnBindAction(
13526 btn.clicker, {action: button}, function(e) {
13527 e.preventDefault();
13528
13529 _fnPageChange( settings, e.data.action, true );
13530 }
13531 );
13532
13533 buttonEls.push(btn.display);
13534 }
13535
13536 var wrapped = _fnRenderer(settings, 'pagingContainer')(
13537 settings, buttonEls
13538 );
13539
13540 var activeEl = host.find(document.activeElement).data('dt-idx');
13541
13542 host.empty().append(wrapped);
13543
13544 if ( activeEl !== undefined ) {
13545 host.find( '[data-dt-idx='+activeEl+']' ).trigger('focus');
13546 }
13547
13548 // Responsive - check if the buttons are over two lines based on the
13549 // height of the buttons and the container.
13550 if (buttonEls.length) {
13551 var outerHeight = $(buttonEls[0]).outerHeight();
13552
13553 if (
13554 opts.buttons > 1 && // prevent infinite
13555 outerHeight > 0 && // will be 0 if hidden
13556 $(host).height() >= (outerHeight * 2) - 10
13557 ) {
13558 _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 }));
13559 }
13560 }
13561 }
13562
13563 /**
13564 * Get properties for a button based on the current paging state of the table
13565 *
13566 * @param {*} settings DT settings object
13567 * @param {*} button The button type in question
13568 * @param {*} page Table's current page
13569 * @param {*} pages Number of pages
13570 * @returns Info object
13571 */
13572 function _pagingButtonInfo(settings, button, page, pages) {
13573 var lang = settings.oLanguage.oPaginate;
13574 var o = {
13575 display: '',
13576 active: false,
13577 disabled: false
13578 };
13579
13580 switch ( button ) {
13581 case 'ellipsis':
13582 o.display = '&#x2026;';
13583 break;
13584
13585 case 'first':
13586 o.display = lang.sFirst;
13587
13588 if (page === 0) {
13589 o.disabled = true;
13590 }
13591 break;
13592
13593 case 'previous':
13594 o.display = lang.sPrevious;
13595
13596 if ( page === 0 ) {
13597 o.disabled = true;
13598 }
13599 break;
13600
13601 case 'next':
13602 o.display = lang.sNext;
13603
13604 if ( pages === 0 || page === pages-1 ) {
13605 o.disabled = true;
13606 }
13607 break;
13608
13609 case 'last':
13610 o.display = lang.sLast;
13611
13612 if ( pages === 0 || page === pages-1 ) {
13613 o.disabled = true;
13614 }
13615 break;
13616
13617 default:
13618 if ( typeof button === 'number' ) {
13619 o.display = settings.fnFormatNumber( button + 1 );
13620
13621 if (page === button) {
13622 o.active = true;
13623 }
13624 }
13625 break;
13626 }
13627
13628 return o;
13629 }
13630
13631 /**
13632 * Compute what number buttons to show in the paging control
13633 *
13634 * @param {*} page Current page
13635 * @param {*} pages Total number of pages
13636 * @param {*} buttons Target number of number buttons
13637 * @param {boolean} addFirstLast Indicate if page 1 and end should be included
13638 * @returns Buttons to show
13639 */
13640 function _pagingNumbers ( page, pages, buttons, addFirstLast ) {
13641 var
13642 numbers = [],
13643 half = Math.floor(buttons / 2),
13644 before = addFirstLast ? 2 : 1,
13645 after = addFirstLast ? 1 : 0;
13646
13647 if ( pages <= buttons ) {
13648 numbers = _range(0, pages);
13649 }
13650 else if (buttons === 1) {
13651 // Single button - current page only
13652 numbers = [page];
13653 }
13654 else if (buttons === 3) {
13655 // Special logic for just three buttons
13656 if (page <= 1) {
13657 numbers = [0, 1, 'ellipsis'];
13658 }
13659 else if (page >= pages - 2) {
13660 numbers = _range(pages-2, pages);
13661 numbers.unshift('ellipsis');
13662 }
13663 else {
13664 numbers = ['ellipsis', page, 'ellipsis'];
13665 }
13666 }
13667 else if ( page <= half ) {
13668 numbers = _range(0, buttons-before);
13669 numbers.push('ellipsis');
13670
13671 if (addFirstLast) {
13672 numbers.push(pages-1);
13673 }
13674 }
13675 else if ( page >= pages - 1 - half ) {
13676 numbers = _range(pages-(buttons-before), pages);
13677 numbers.unshift('ellipsis');
13678
13679 if (addFirstLast) {
13680 numbers.unshift(0);
13681 }
13682 }
13683 else {
13684 numbers = _range(page-half+before, page+half-after);
13685 numbers.push('ellipsis');
13686 numbers.unshift('ellipsis');
13687
13688 if (addFirstLast) {
13689 numbers.push(pages-1);
13690 numbers.unshift(0);
13691 }
13692 }
13693
13694 return numbers;
13695 }
13696
13697 var __lengthCounter = 0;
13698
13699 // opts
13700 // - menu
13701 // - text
13702 DataTable.feature.register( 'pageLength', function ( settings, opts ) {
13703 var features = settings.oFeatures;
13704
13705 // For compatibility with the legacy `pageLength` top level option
13706 if (! features.bPaginate || ! features.bLengthChange) {
13707 return null;
13708 }
13709
13710 opts = $.extend({
13711 menu: settings.aLengthMenu,
13712 text: settings.oLanguage.sLengthMenu
13713 }, opts);
13714
13715 var
13716 classes = settings.oClasses.length,
13717 tableId = settings.sTableId,
13718 menu = opts.menu,
13719 lengths = [],
13720 language = [],
13721 i;
13722
13723 // Options can be given in a number of ways
13724 if (Array.isArray( menu[0] )) {
13725 // Old 1.x style - 2D array
13726 lengths = menu[0];
13727 language = menu[1];
13728 }
13729 else {
13730 for ( i=0 ; i<menu.length ; i++ ) {
13731 // An object with different label and value
13732 if ($.isPlainObject(menu[i])) {
13733 lengths.push(menu[i].value);
13734 language.push(menu[i].label);
13735 }
13736 else {
13737 // Or just a number to display and use
13738 lengths.push(menu[i]);
13739 language.push(menu[i]);
13740 }
13741 }
13742 }
13743
13744 // We can put the <select> outside of the label if it is at the start or
13745 // end which helps improve accessability (not all screen readers like
13746 // implicit for elements).
13747 var end = opts.text.match(/_MENU_$/);
13748 var start = opts.text.match(/^_MENU_/);
13749 var removed = opts.text.replace(/_MENU_/, '');
13750 var str = '<label>' + opts.text + '</label>';
13751
13752 if (start) {
13753 str = '_MENU_<label>' + removed + '</label>';
13754 }
13755 else if (end) {
13756 str = '<label>' + removed + '</label>_MENU_';
13757 }
13758
13759 // Wrapper element - use a span as a holder for where the select will go
13760 var tmpId = 'tmp-' + (+new Date())
13761 var div = $('<div/>')
13762 .addClass( classes.container )
13763 .append(
13764 str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' )
13765 );
13766
13767 // Save text node content for macro updating
13768 var textNodes = [];
13769 Array.prototype.slice.call(div.find('label')[0].childNodes).forEach(function (el) {
13770 if (el.nodeType === Node.TEXT_NODE) {
13771 textNodes.push({
13772 el: el,
13773 text: el.textContent
13774 });
13775 }
13776 });
13777
13778 // Update the label text in case it has an entries value
13779 var updateEntries = function (len) {
13780 textNodes.forEach(function (node) {
13781 node.el.textContent = _fnMacros(settings, node.text, len);
13782 });
13783 }
13784
13785 // Next, the select itself, along with the options
13786 var select = $('<select/>', {
13787 'aria-controls': tableId,
13788 'class': classes.select
13789 } );
13790
13791 for ( i=0 ; i<lengths.length ; i++ ) {
13792 select[0][ i ] = new Option(
13793 typeof language[i] === 'number' ?
13794 settings.fnFormatNumber( language[i] ) :
13795 language[i],
13796 lengths[i]
13797 );
13798 }
13799
13800 // add for and id to label and input
13801 div.find('label').attr('for', 'dt-length-' + __lengthCounter);
13802 select.attr('id', 'dt-length-' + __lengthCounter);
13803 __lengthCounter++;
13804
13805 // Swap in the select list
13806 div.find('#' + tmpId).replaceWith(select);
13807
13808 // Can't use `select` variable as user might provide their own and the
13809 // reference is broken by the use of outerHTML
13810 $('select', div)
13811 .val( settings._iDisplayLength )
13812 .on( 'change.DT', function() {
13813 _fnLengthChange( settings, $(this).val() );
13814 _fnDraw( settings );
13815 } );
13816
13817 // Update node value whenever anything changes the table's length
13818 $(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
13819 if ( settings === s ) {
13820 $('select', div).val( len );
13821
13822 // Resolve plurals in the text for the new length
13823 updateEntries(len);
13824 }
13825 } );
13826
13827 updateEntries(settings._iDisplayLength);
13828
13829 return div;
13830 }, 'l' );
13831
13832 // jQuery access
13833 $.fn.dataTable = DataTable;
13834
13835 // Provide access to the host jQuery object (circular reference)
13836 DataTable.$ = $;
13837
13838 // Legacy aliases
13839 $.fn.dataTableSettings = DataTable.settings;
13840 $.fn.dataTableExt = DataTable.ext;
13841
13842 // With a capital `D` we return a DataTables API instance rather than a
13843 // jQuery object
13844 $.fn.DataTable = function ( opts ) {
13845 return $(this).dataTable( opts ).api();
13846 };
13847
13848 // All properties that are available to $.fn.dataTable should also be
13849 // available on $.fn.DataTable
13850 $.each( DataTable, function ( prop, val ) {
13851 $.fn.DataTable[ prop ] = val;
13852 } );
13853
13854 return DataTable;
13855 }));
13856
13857
13858 /*! DataTables styling integration
13859 * © SpryMedia Ltd - datatables.net/license
13860 */
13861
13862 (function( factory ){
13863 if ( typeof define === 'function' && define.amd ) {
13864 // AMD
13865 define( ['jquery', 'datatables.net'], function ( $ ) {
13866 return factory( $, window, document );
13867 } );
13868 }
13869 else if ( typeof exports === 'object' ) {
13870 // CommonJS
13871 var jq = require('jquery');
13872 var cjsRequires = function (root, $) {
13873 if ( ! $.fn.dataTable ) {
13874 require('datatables.net')(root, $);
13875 }
13876 };
13877
13878 if (typeof window === 'undefined') {
13879 module.exports = function (root, $) {
13880 if ( ! root ) {
13881 // CommonJS environments without a window global must pass a
13882 // root. This will give an error otherwise
13883 root = window;
13884 }
13885
13886 if ( ! $ ) {
13887 $ = jq( root );
13888 }
13889
13890 cjsRequires( root, $ );
13891 return factory( $, root, root.document );
13892 };
13893 }
13894 else {
13895 cjsRequires( window, jq );
13896 module.exports = factory( jq, window, window.document );
13897 }
13898 }
13899 else {
13900 // Browser
13901 factory( jQuery, window, document );
13902 }
13903 }(function( $, window, document ) {
13904 'use strict';
13905 var DataTable = $.fn.dataTable;
13906
13907
13908
13909
13910
13911 return DataTable;
13912 }));
13913
13914
13915 /*! Buttons for DataTables 3.2.2
13916 * © SpryMedia Ltd - datatables.net/license
13917 */
13918
13919 (function( factory ){
13920 if ( typeof define === 'function' && define.amd ) {
13921 // AMD
13922 define( ['jquery', 'datatables.net'], function ( $ ) {
13923 return factory( $, window, document );
13924 } );
13925 }
13926 else if ( typeof exports === 'object' ) {
13927 // CommonJS
13928 var jq = require('jquery');
13929 var cjsRequires = function (root, $) {
13930 if ( ! $.fn.dataTable ) {
13931 require('datatables.net')(root, $);
13932 }
13933 };
13934
13935 if (typeof window === 'undefined') {
13936 module.exports = function (root, $) {
13937 if ( ! root ) {
13938 // CommonJS environments without a window global must pass a
13939 // root. This will give an error otherwise
13940 root = window;
13941 }
13942
13943 if ( ! $ ) {
13944 $ = jq( root );
13945 }
13946
13947 cjsRequires( root, $ );
13948 return factory( $, root, root.document );
13949 };
13950 }
13951 else {
13952 cjsRequires( window, jq );
13953 module.exports = factory( jq, window, window.document );
13954 }
13955 }
13956 else {
13957 // Browser
13958 factory( jQuery, window, document );
13959 }
13960 }(function( $, window, document ) {
13961 'use strict';
13962 var DataTable = $.fn.dataTable;
13963
13964
13965
13966 // Used for namespacing events added to the document by each instance, so they
13967 // can be removed on destroy
13968 var _instCounter = 0;
13969
13970 // Button namespacing counter for namespacing events on individual buttons
13971 var _buttonCounter = 0;
13972
13973 var _dtButtons = DataTable.ext.buttons;
13974
13975 // Custom entity decoder for data export
13976 var _entityDecoder = null;
13977
13978 // Allow for jQuery slim
13979 function _fadeIn(el, duration, fn) {
13980 if ($.fn.animate) {
13981 el.stop().fadeIn(duration, fn);
13982 }
13983 else {
13984 el.css('display', 'block');
13985
13986 if (fn) {
13987 fn.call(el);
13988 }
13989 }
13990 }
13991
13992 function _fadeOut(el, duration, fn) {
13993 if ($.fn.animate) {
13994 el.stop().fadeOut(duration, fn);
13995 }
13996 else {
13997 el.css('display', 'none');
13998
13999 if (fn) {
14000 fn.call(el);
14001 }
14002 }
14003 }
14004
14005 /**
14006 * [Buttons description]
14007 * @param {[type]}
14008 * @param {[type]}
14009 */
14010 var Buttons = function (dt, config) {
14011 if (!DataTable.versionCheck('2')) {
14012 throw 'Warning: Buttons requires DataTables 2 or newer';
14013 }
14014
14015 // If not created with a `new` keyword then we return a wrapper function that
14016 // will take the settings object for a DT. This allows easy use of new instances
14017 // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
14018 if (!(this instanceof Buttons)) {
14019 return function (settings) {
14020 return new Buttons(settings, dt).container();
14021 };
14022 }
14023
14024 // If there is no config set it to an empty object
14025 if (typeof config === 'undefined') {
14026 config = {};
14027 }
14028
14029 // Allow a boolean true for defaults
14030 if (config === true) {
14031 config = {};
14032 }
14033
14034 // For easy configuration of buttons an array can be given
14035 if (Array.isArray(config)) {
14036 config = { buttons: config };
14037 }
14038
14039 this.c = $.extend(true, {}, Buttons.defaults, config);
14040
14041 // Don't want a deep copy for the buttons
14042 if (config.buttons) {
14043 this.c.buttons = config.buttons;
14044 }
14045
14046 this.s = {
14047 dt: new DataTable.Api(dt),
14048 buttons: [],
14049 listenKeys: '',
14050 namespace: 'dtb' + _instCounter++
14051 };
14052
14053 this.dom = {
14054 container: $('<' + this.c.dom.container.tag + '/>').addClass(
14055 this.c.dom.container.className
14056 )
14057 };
14058
14059 this._constructor();
14060 };
14061
14062 $.extend(Buttons.prototype, {
14063 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14064 * Public methods
14065 */
14066
14067 /**
14068 * Get the action of a button
14069 * @param {int|string} Button index
14070 * @return {function}
14071 */ /**
14072 * Set the action of a button
14073 * @param {node} node Button element
14074 * @param {function} action Function to set
14075 * @return {Buttons} Self for chaining
14076 */
14077 action: function (node, action) {
14078 var button = this._nodeToButton(node);
14079
14080 if (action === undefined) {
14081 return button.conf.action;
14082 }
14083
14084 button.conf.action = action;
14085
14086 return this;
14087 },
14088
14089 /**
14090 * Add an active class to the button to make to look active or get current
14091 * active state.
14092 * @param {node} node Button element
14093 * @param {boolean} [flag] Enable / disable flag
14094 * @return {Buttons} Self for chaining or boolean for getter
14095 */
14096 active: function (node, flag) {
14097 var button = this._nodeToButton(node);
14098 var klass = this.c.dom.button.active;
14099 var jqNode = $(button.node);
14100
14101 if (
14102 button.inCollection &&
14103 this.c.dom.collection.button &&
14104 this.c.dom.collection.button.active !== undefined
14105 ) {
14106 klass = this.c.dom.collection.button.active;
14107 }
14108
14109 if (flag === undefined) {
14110 return jqNode.hasClass(klass);
14111 }
14112
14113 jqNode.toggleClass(klass, flag === undefined ? true : flag);
14114
14115 return this;
14116 },
14117
14118 /**
14119 * Add a new button
14120 * @param {object} config Button configuration object, base string name or function
14121 * @param {int|string} [idx] Button index for where to insert the button
14122 * @param {boolean} [draw=true] Trigger a draw. Set a false when adding
14123 * lots of buttons, until the last button.
14124 * @return {Buttons} Self for chaining
14125 */
14126 add: function (config, idx, draw) {
14127 var buttons = this.s.buttons;
14128
14129 if (typeof idx === 'string') {
14130 var split = idx.split('-');
14131 var base = this.s;
14132
14133 for (var i = 0, ien = split.length - 1; i < ien; i++) {
14134 base = base.buttons[split[i] * 1];
14135 }
14136
14137 buttons = base.buttons;
14138 idx = split[split.length - 1] * 1;
14139 }
14140
14141 this._expandButton(
14142 buttons,
14143 config,
14144 config !== undefined ? config.split : undefined,
14145 (config === undefined ||
14146 config.split === undefined ||
14147 config.split.length === 0) &&
14148 base !== undefined,
14149 false,
14150 idx
14151 );
14152
14153 if (draw === undefined || draw === true) {
14154 this._draw();
14155 }
14156
14157 return this;
14158 },
14159
14160 /**
14161 * Clear buttons from a collection and then insert new buttons
14162 */
14163 collectionRebuild: function (node, newButtons) {
14164 var button = this._nodeToButton(node);
14165
14166 if (newButtons !== undefined) {
14167 var i;
14168 // Need to reverse the array
14169 for (i = button.buttons.length - 1; i >= 0; i--) {
14170 this.remove(button.buttons[i].node);
14171 }
14172
14173 // If the collection has prefix and / or postfix buttons we need to add them in
14174 if (button.conf.prefixButtons) {
14175 newButtons.unshift.apply(newButtons, button.conf.prefixButtons);
14176 }
14177
14178 if (button.conf.postfixButtons) {
14179 newButtons.push.apply(newButtons, button.conf.postfixButtons);
14180 }
14181
14182 for (i = 0; i < newButtons.length; i++) {
14183 var newBtn = newButtons[i];
14184
14185 this._expandButton(
14186 button.buttons,
14187 newBtn,
14188 newBtn !== undefined &&
14189 newBtn.config !== undefined &&
14190 newBtn.config.split !== undefined,
14191 true,
14192 newBtn.parentConf !== undefined &&
14193 newBtn.parentConf.split !== undefined,
14194 null,
14195 newBtn.parentConf
14196 );
14197 }
14198 }
14199
14200 this._draw(button.collection, button.buttons);
14201 },
14202
14203 /**
14204 * Get the container node for the buttons
14205 * @return {jQuery} Buttons node
14206 */
14207 container: function () {
14208 return this.dom.container;
14209 },
14210
14211 /**
14212 * Disable a button
14213 * @param {node} node Button node
14214 * @return {Buttons} Self for chaining
14215 */
14216 disable: function (node) {
14217 var button = this._nodeToButton(node);
14218
14219 if (button.isSplit) {
14220 $(button.node.childNodes[0])
14221 .addClass(this.c.dom.button.disabled)
14222 .prop('disabled', true);
14223 }
14224 else {
14225 $(button.node)
14226 .addClass(this.c.dom.button.disabled)
14227 .prop('disabled', true);
14228 }
14229
14230 button.disabled = true;
14231
14232 this._checkSplitEnable();
14233
14234 return this;
14235 },
14236
14237 /**
14238 * Destroy the instance, cleaning up event handlers and removing DOM
14239 * elements
14240 * @return {Buttons} Self for chaining
14241 */
14242 destroy: function () {
14243 // Key event listener
14244 $('body').off('keyup.' + this.s.namespace);
14245
14246 // Individual button destroy (so they can remove their own events if
14247 // needed). Take a copy as the array is modified by `remove`
14248 var buttons = this.s.buttons.slice();
14249 var i, ien;
14250
14251 for (i = 0, ien = buttons.length; i < ien; i++) {
14252 this.remove(buttons[i].node);
14253 }
14254
14255 // Container
14256 this.dom.container.remove();
14257
14258 // Remove from the settings object collection
14259 var buttonInsts = this.s.dt.settings()[0];
14260
14261 for (i = 0, ien = buttonInsts.length; i < ien; i++) {
14262 if (buttonInsts.inst === this) {
14263 buttonInsts.splice(i, 1);
14264 break;
14265 }
14266 }
14267
14268 return this;
14269 },
14270
14271 /**
14272 * Enable / disable a button
14273 * @param {node} node Button node
14274 * @param {boolean} [flag=true] Enable / disable flag
14275 * @return {Buttons} Self for chaining
14276 */
14277 enable: function (node, flag) {
14278 if (flag === false) {
14279 return this.disable(node);
14280 }
14281
14282 var button = this._nodeToButton(node);
14283
14284 if (button.isSplit) {
14285 $(button.node.childNodes[0])
14286 .removeClass(this.c.dom.button.disabled)
14287 .prop('disabled', false);
14288 }
14289 else {
14290 $(button.node)
14291 .removeClass(this.c.dom.button.disabled)
14292 .prop('disabled', false);
14293 }
14294
14295 button.disabled = false;
14296
14297 this._checkSplitEnable();
14298
14299 return this;
14300 },
14301
14302 /**
14303 * Get a button's index
14304 *
14305 * This is internally recursive
14306 * @param {element} node Button to get the index of
14307 * @return {string} Button index
14308 */
14309 index: function (node, nested, buttons) {
14310 if (!nested) {
14311 nested = '';
14312 buttons = this.s.buttons;
14313 }
14314
14315 for (var i = 0, ien = buttons.length; i < ien; i++) {
14316 var inner = buttons[i].buttons;
14317
14318 if (buttons[i].node === node) {
14319 return nested + i;
14320 }
14321
14322 if (inner && inner.length) {
14323 var match = this.index(node, i + '-', inner);
14324
14325 if (match !== null) {
14326 return match;
14327 }
14328 }
14329 }
14330
14331 return null;
14332 },
14333
14334 /**
14335 * Get the instance name for the button set selector
14336 * @return {string} Instance name
14337 */
14338 name: function () {
14339 return this.c.name;
14340 },
14341
14342 /**
14343 * Get a button's node of the buttons container if no button is given
14344 * @param {node} [node] Button node
14345 * @return {jQuery} Button element, or container
14346 */
14347 node: function (node) {
14348 if (!node) {
14349 return this.dom.container;
14350 }
14351
14352 var button = this._nodeToButton(node);
14353 return $(button.node);
14354 },
14355
14356 /**
14357 * Set / get a processing class on the selected button
14358 * @param {element} node Triggering button node
14359 * @param {boolean} flag true to add, false to remove, undefined to get
14360 * @return {boolean|Buttons} Getter value or this if a setter.
14361 */
14362 processing: function (node, flag) {
14363 var dt = this.s.dt;
14364 var button = this._nodeToButton(node);
14365
14366 if (flag === undefined) {
14367 return $(button.node).hasClass('processing');
14368 }
14369
14370 $(button.node).toggleClass('processing', flag);
14371
14372 $(dt.table().node()).triggerHandler('buttons-processing.dt', [
14373 flag,
14374 dt.button(node),
14375 dt,
14376 $(node),
14377 button.conf
14378 ]);
14379
14380 return this;
14381 },
14382
14383 /**
14384 * Remove a button.
14385 * @param {node} node Button node
14386 * @return {Buttons} Self for chaining
14387 */
14388 remove: function (node) {
14389 var button = this._nodeToButton(node);
14390 var host = this._nodeToHost(node);
14391 var dt = this.s.dt;
14392
14393 // Remove any child buttons first
14394 if (button.buttons.length) {
14395 for (var i = button.buttons.length - 1; i >= 0; i--) {
14396 this.remove(button.buttons[i].node);
14397 }
14398 }
14399
14400 button.conf.destroying = true;
14401
14402 // Allow the button to remove event handlers, etc
14403 if (button.conf.destroy) {
14404 button.conf.destroy.call(dt.button(node), dt, $(node), button.conf);
14405 }
14406
14407 this._removeKey(button.conf);
14408
14409 $(button.node).remove();
14410
14411 var idx = $.inArray(button, host);
14412 host.splice(idx, 1);
14413
14414 return this;
14415 },
14416
14417 /**
14418 * Get the text for a button
14419 * @param {int|string} node Button index
14420 * @return {string} Button text
14421 */ /**
14422 * Set the text for a button
14423 * @param {int|string|function} node Button index
14424 * @param {string} label Text
14425 * @return {Buttons} Self for chaining
14426 */
14427 text: function (node, label) {
14428 var button = this._nodeToButton(node);
14429 var textNode = button.textNode;
14430 var dt = this.s.dt;
14431 var jqNode = $(button.node);
14432 var text = function (opt) {
14433 return typeof opt === 'function'
14434 ? opt(dt, jqNode, button.conf)
14435 : opt;
14436 };
14437
14438 if (label === undefined) {
14439 return text(button.conf.text);
14440 }
14441
14442 button.conf.text = label;
14443 textNode.html(text(label));
14444
14445 return this;
14446 },
14447
14448 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14449 * Constructor
14450 */
14451
14452 /**
14453 * Buttons constructor
14454 * @private
14455 */
14456 _constructor: function () {
14457 var that = this;
14458 var dt = this.s.dt;
14459 var dtSettings = dt.settings()[0];
14460 var buttons = this.c.buttons;
14461
14462 if (!dtSettings._buttons) {
14463 dtSettings._buttons = [];
14464 }
14465
14466 dtSettings._buttons.push({
14467 inst: this,
14468 name: this.c.name
14469 });
14470
14471 for (var i = 0, ien = buttons.length; i < ien; i++) {
14472 this.add(buttons[i]);
14473 }
14474
14475 dt.on('destroy', function (e, settings) {
14476 if (settings === dtSettings) {
14477 that.destroy();
14478 }
14479 });
14480
14481 // Global key event binding to listen for button keys
14482 $('body').on('keyup.' + this.s.namespace, function (e) {
14483 if (
14484 !document.activeElement ||
14485 document.activeElement === document.body
14486 ) {
14487 // SUse a string of characters for fast lookup of if we need to
14488 // handle this
14489 var character = String.fromCharCode(e.keyCode).toLowerCase();
14490
14491 if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
14492 that._keypress(character, e);
14493 }
14494 }
14495 });
14496 },
14497
14498 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14499 * Private methods
14500 */
14501
14502 /**
14503 * Add a new button to the key press listener
14504 * @param {object} conf Resolved button configuration object
14505 * @private
14506 */
14507 _addKey: function (conf) {
14508 if (conf.key) {
14509 this.s.listenKeys += $.isPlainObject(conf.key)
14510 ? conf.key.key
14511 : conf.key;
14512 }
14513 },
14514
14515 /**
14516 * Insert the buttons into the container. Call without parameters!
14517 * @param {node} [container] Recursive only - Insert point
14518 * @param {array} [buttons] Recursive only - Buttons array
14519 * @private
14520 */
14521 _draw: function (container, buttons) {
14522 if (!container) {
14523 container = this.dom.container;
14524 buttons = this.s.buttons;
14525 }
14526
14527 container.children().detach();
14528
14529 for (var i = 0, ien = buttons.length; i < ien; i++) {
14530 container.append(buttons[i].inserter);
14531 container.append(' ');
14532
14533 if (buttons[i].buttons && buttons[i].buttons.length) {
14534 this._draw(buttons[i].collection, buttons[i].buttons);
14535 }
14536 }
14537 },
14538
14539 /**
14540 * Create buttons from an array of buttons
14541 * @param {array} attachTo Buttons array to attach to
14542 * @param {object} button Button definition
14543 * @param {boolean} inCollection true if the button is in a collection
14544 * @private
14545 */
14546 _expandButton: function (
14547 attachTo,
14548 button,
14549 split,
14550 inCollection,
14551 inSplit,
14552 attachPoint,
14553 parentConf
14554 ) {
14555 var dt = this.s.dt;
14556 var isSplit = false;
14557 var domCollection = this.c.dom.collection;
14558 var buttons = !Array.isArray(button) ? [button] : button;
14559
14560 if (button === undefined) {
14561 buttons = !Array.isArray(split) ? [split] : split;
14562 }
14563
14564 for (var i = 0, ien = buttons.length; i < ien; i++) {
14565 var conf = this._resolveExtends(buttons[i]);
14566
14567 if (!conf) {
14568 continue;
14569 }
14570
14571 isSplit = conf.config && conf.config.split ? true : false;
14572
14573 // If the configuration is an array, then expand the buttons at this
14574 // point
14575 if (Array.isArray(conf)) {
14576 this._expandButton(
14577 attachTo,
14578 conf,
14579 built !== undefined && built.conf !== undefined
14580 ? built.conf.split
14581 : undefined,
14582 inCollection,
14583 parentConf !== undefined && parentConf.split !== undefined,
14584 attachPoint,
14585 parentConf
14586 );
14587 continue;
14588 }
14589
14590 var built = this._buildButton(
14591 conf,
14592 inCollection,
14593 conf.split !== undefined ||
14594 (conf.config !== undefined &&
14595 conf.config.split !== undefined),
14596 inSplit
14597 );
14598 if (!built) {
14599 continue;
14600 }
14601
14602 if (attachPoint !== undefined && attachPoint !== null) {
14603 attachTo.splice(attachPoint, 0, built);
14604 attachPoint++;
14605 }
14606 else {
14607 attachTo.push(built);
14608 }
14609
14610 // Any button type can have a drop icon set
14611 if (built.conf.dropIcon && ! built.conf.split) {
14612 $(built.node)
14613 .addClass(this.c.dom.button.dropClass)
14614 .append(this.c.dom.button.dropHtml);
14615 }
14616
14617 // Create the dropdown for a collection
14618 if (built.conf.buttons) {
14619 built.collection = $(
14620 '<' + domCollection.container.content.tag + '/>'
14621 );
14622 built.conf._collection = built.collection;
14623
14624 this._expandButton(
14625 built.buttons,
14626 built.conf.buttons,
14627 built.conf.split,
14628 !isSplit,
14629 isSplit,
14630 attachPoint,
14631 built.conf
14632 );
14633 }
14634
14635 // And the split collection
14636 if (built.conf.split) {
14637 built.collection = $('<' + domCollection.container.tag + '/>');
14638 built.conf._collection = built.collection;
14639
14640 for (var j = 0; j < built.conf.split.length; j++) {
14641 var item = built.conf.split[j];
14642
14643 if (typeof item === 'object') {
14644 item.parent = parentConf;
14645
14646 if (item.collectionLayout === undefined) {
14647 item.collectionLayout = built.conf.collectionLayout;
14648 }
14649
14650 if (item.dropup === undefined) {
14651 item.dropup = built.conf.dropup;
14652 }
14653
14654 if (item.fade === undefined) {
14655 item.fade = built.conf.fade;
14656 }
14657 }
14658 }
14659
14660 this._expandButton(
14661 built.buttons,
14662 built.conf.buttons,
14663 built.conf.split,
14664 !isSplit,
14665 isSplit,
14666 attachPoint,
14667 built.conf
14668 );
14669 }
14670
14671 built.conf.parent = parentConf;
14672
14673 // init call is made here, rather than buildButton as it needs to
14674 // be selectable, and for that it needs to be in the buttons array
14675 if (conf.init) {
14676 conf.init.call(dt.button(built.node), dt, $(built.node), conf);
14677 }
14678 }
14679 },
14680
14681 /**
14682 * Create an individual button
14683 * @param {object} config Resolved button configuration
14684 * @param {boolean} inCollection `true` if a collection button
14685 * @return {object} Completed button description object
14686 * @private
14687 */
14688 _buildButton: function (config, inCollection, isSplit, inSplit) {
14689 var that = this;
14690 var configDom = this.c.dom;
14691 var textNode;
14692 var dt = this.s.dt;
14693 var text = function (opt) {
14694 return typeof opt === 'function' ? opt(dt, button, config) : opt;
14695 };
14696
14697 // Create an object that describes the button which can be in `dom.button`, or
14698 // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`!
14699 // Each should extend from `dom.button`.
14700 var dom = $.extend(true, {}, configDom.button);
14701
14702 if (inCollection && isSplit && configDom.collection.split) {
14703 $.extend(true, dom, configDom.collection.split.action);
14704 }
14705 else if (inSplit || inCollection) {
14706 $.extend(true, dom, configDom.collection.button);
14707 }
14708 else if (isSplit) {
14709 $.extend(true, dom, configDom.split.button);
14710 }
14711
14712 // Spacers don't do much other than insert an element into the DOM
14713 if (config.spacer) {
14714 var spacer = $('<' + dom.spacer.tag + '/>')
14715 .addClass(
14716 'dt-button-spacer ' +
14717 config.style +
14718 ' ' +
14719 dom.spacer.className
14720 )
14721 .html(text(config.text));
14722
14723 return {
14724 conf: config,
14725 node: spacer,
14726 inserter: spacer,
14727 buttons: [],
14728 inCollection: inCollection,
14729 isSplit: isSplit,
14730 collection: null,
14731 textNode: spacer
14732 };
14733 }
14734
14735 // Make sure that the button is available based on whatever requirements
14736 // it has. For example, PDF button require pdfmake
14737 if (
14738 config.available &&
14739 !config.available(dt, config) &&
14740 !config.html
14741 ) {
14742 return false;
14743 }
14744
14745 var button;
14746
14747 if (!config.html) {
14748 var run = function (e, dt, button, config, done) {
14749 config.action.call(dt.button(button), e, dt, button, config, done);
14750
14751 $(dt.table().node()).triggerHandler('buttons-action.dt', [
14752 dt.button(button),
14753 dt,
14754 button,
14755 config
14756 ]);
14757 };
14758
14759 var action = function(e, dt, button, config) {
14760 if (config.async) {
14761 that.processing(button[0], true);
14762
14763 setTimeout(function () {
14764 run(e, dt, button, config, function () {
14765 that.processing(button[0], false);
14766 });
14767 }, config.async);
14768 }
14769 else {
14770 run(e, dt, button, config, function () {});
14771 }
14772 };
14773
14774 var tag = config.tag || dom.tag;
14775 var clickBlurs =
14776 config.clickBlurs === undefined ? true : config.clickBlurs;
14777
14778 button = $('<' + tag + '/>')
14779 .addClass(dom.className)
14780 .attr('tabindex', this.s.dt.settings()[0].iTabIndex)
14781 .attr('aria-controls', this.s.dt.table().node().id)
14782 .on('click.dtb', function (e) {
14783 e.preventDefault();
14784
14785 if (!button.hasClass(dom.disabled) && config.action) {
14786 action(e, dt, button, config);
14787 }
14788
14789 if (clickBlurs) {
14790 button.trigger('blur');
14791 }
14792 })
14793 .on('keypress.dtb', function (e) {
14794 if (e.keyCode === 13) {
14795 e.preventDefault();
14796
14797 if (!button.hasClass(dom.disabled) && config.action) {
14798 action(e, dt, button, config);
14799 }
14800 }
14801 });
14802
14803 // Make `a` tags act like a link
14804 if (tag.toLowerCase() === 'a') {
14805 button.attr('href', '#');
14806 }
14807
14808 // Button tags should have `type=button` so they don't have any default behaviour
14809 if (tag.toLowerCase() === 'button') {
14810 button.attr('type', 'button');
14811 }
14812
14813 if (dom.liner.tag) {
14814 var liner = $('<' + dom.liner.tag + '/>')
14815 .html(text(config.text))
14816 .addClass(dom.liner.className);
14817
14818 if (dom.liner.tag.toLowerCase() === 'a') {
14819 liner.attr('href', '#');
14820 }
14821
14822 button.append(liner);
14823 textNode = liner;
14824 }
14825 else {
14826 button.html(text(config.text));
14827 textNode = button;
14828 }
14829
14830 if (config.enabled === false) {
14831 button.addClass(dom.disabled);
14832 }
14833
14834 if (config.className) {
14835 button.addClass(config.className);
14836 }
14837
14838 if (config.titleAttr) {
14839 button.attr('title', text(config.titleAttr));
14840 }
14841
14842 if (config.attr) {
14843 button.attr(config.attr);
14844 }
14845
14846 if (!config.namespace) {
14847 config.namespace = '.dt-button-' + _buttonCounter++;
14848 }
14849
14850 if (config.config !== undefined && config.config.split) {
14851 config.split = config.config.split;
14852 }
14853 }
14854 else {
14855 button = $(config.html);
14856 }
14857
14858 var buttonContainer = this.c.dom.buttonContainer;
14859 var inserter;
14860 if (buttonContainer && buttonContainer.tag) {
14861 inserter = $('<' + buttonContainer.tag + '/>')
14862 .addClass(buttonContainer.className)
14863 .append(button);
14864 }
14865 else {
14866 inserter = button;
14867 }
14868
14869 this._addKey(config);
14870
14871 // Style integration callback for DOM manipulation
14872 // Note that this is _not_ documented. It is currently
14873 // for style integration only
14874 if (this.c.buttonCreated) {
14875 inserter = this.c.buttonCreated(config, inserter);
14876 }
14877
14878 var splitDiv;
14879
14880 if (isSplit) {
14881 var dropdownConf = inCollection
14882 ? $.extend(true, this.c.dom.split, this.c.dom.collection.split)
14883 : this.c.dom.split;
14884 var wrapperConf = dropdownConf.wrapper;
14885
14886 splitDiv = $('<' + wrapperConf.tag + '/>')
14887 .addClass(wrapperConf.className)
14888 .append(button);
14889
14890 var dropButtonConfig = $.extend(config, {
14891 autoClose: true,
14892 align: dropdownConf.dropdown.align,
14893 attr: {
14894 'aria-haspopup': 'dialog',
14895 'aria-expanded': false
14896 },
14897 className: dropdownConf.dropdown.className,
14898 closeButton: false,
14899 splitAlignClass: dropdownConf.dropdown.splitAlignClass,
14900 text: dropdownConf.dropdown.text
14901 });
14902
14903 this._addKey(dropButtonConfig);
14904
14905 var splitAction = function (e, dt, button, config) {
14906 _dtButtons.split.action.call(
14907 dt.button(splitDiv),
14908 e,
14909 dt,
14910 button,
14911 config
14912 );
14913
14914 $(dt.table().node()).triggerHandler('buttons-action.dt', [
14915 dt.button(button),
14916 dt,
14917 button,
14918 config
14919 ]);
14920 button.attr('aria-expanded', true);
14921 };
14922
14923 var dropButton = $(
14924 '<button class="' +
14925 dropdownConf.dropdown.className +
14926 ' dt-button"></button>'
14927 )
14928 .html(this.c.dom.button.dropHtml)
14929 .addClass(this.c.dom.button.dropClass)
14930 .on('click.dtb', function (e) {
14931 e.preventDefault();
14932 e.stopPropagation();
14933
14934 if (!dropButton.hasClass(dom.disabled)) {
14935 splitAction(e, dt, dropButton, dropButtonConfig);
14936 }
14937 if (clickBlurs) {
14938 dropButton.trigger('blur');
14939 }
14940 })
14941 .on('keypress.dtb', function (e) {
14942 if (e.keyCode === 13) {
14943 e.preventDefault();
14944
14945 if (!dropButton.hasClass(dom.disabled)) {
14946 splitAction(e, dt, dropButton, dropButtonConfig);
14947 }
14948 }
14949 });
14950
14951 if (config.split.length === 0) {
14952 dropButton.addClass('dtb-hide-drop');
14953 }
14954
14955 splitDiv.append(dropButton).attr(dropButtonConfig.attr);
14956 }
14957
14958 return {
14959 conf: config,
14960 node: isSplit ? splitDiv.get(0) : button.get(0),
14961 inserter: isSplit ? splitDiv : inserter,
14962 buttons: [],
14963 inCollection: inCollection,
14964 isSplit: isSplit,
14965 inSplit: inSplit,
14966 collection: null,
14967 textNode: textNode
14968 };
14969 },
14970
14971 /**
14972 * Spin over buttons checking if splits should be enabled or not.
14973 * @param {*} buttons Array of buttons to check
14974 */
14975 _checkSplitEnable: function (buttons) {
14976 if (! buttons) {
14977 buttons = this.s.buttons;
14978 }
14979
14980 for (var i=0 ; i<buttons.length ; i++) {
14981 var button = buttons[i];
14982
14983 // Check if the button is a split one and if so, determine
14984 // its state
14985 if (button.isSplit) {
14986 var splitBtn = button.node.childNodes[1];
14987
14988 if (this._checkAnyEnabled(button.buttons)) {
14989 // Enable the split
14990 $(splitBtn)
14991 .removeClass(this.c.dom.button.disabled)
14992 .prop('disabled', false);
14993 }
14994 else {
14995 $(splitBtn)
14996 .addClass(this.c.dom.button.disabled)
14997 .prop('disabled', false);
14998 }
14999 }
15000 else if (button.isCollection) {
15001 // Nest down into collections
15002 this._checkSplitEnable(button.buttons);
15003 }
15004 }
15005 },
15006
15007 /**
15008 * Check an array of buttons and see if any are enabled in it
15009 * @param {*} buttons Button array
15010 * @returns true if a button is enabled, false otherwise
15011 */
15012 _checkAnyEnabled: function (buttons) {
15013 for (var i=0 ; i<buttons.length ; i++) {
15014 if (! buttons[i].disabled) {
15015 return true;
15016 }
15017 }
15018
15019 return false;
15020 },
15021
15022 /**
15023 * Get the button object from a node (recursive)
15024 * @param {node} node Button node
15025 * @param {array} [buttons] Button array, uses base if not defined
15026 * @return {object} Button object
15027 * @private
15028 */
15029 _nodeToButton: function (node, buttons) {
15030 if (!buttons) {
15031 buttons = this.s.buttons;
15032 }
15033
15034 for (var i = 0, ien = buttons.length; i < ien; i++) {
15035 if (buttons[i].node === node || $(buttons[i].node).children().eq(0).get(0) === node) {
15036 return buttons[i];
15037 }
15038
15039 if (buttons[i].buttons.length) {
15040 var ret = this._nodeToButton(node, buttons[i].buttons);
15041
15042 if (ret) {
15043 return ret;
15044 }
15045 }
15046 }
15047 },
15048
15049 /**
15050 * Get container array for a button from a button node (recursive)
15051 * @param {node} node Button node
15052 * @param {array} [buttons] Button array, uses base if not defined
15053 * @return {array} Button's host array
15054 * @private
15055 */
15056 _nodeToHost: function (node, buttons) {
15057 if (!buttons) {
15058 buttons = this.s.buttons;
15059 }
15060
15061 for (var i = 0, ien = buttons.length; i < ien; i++) {
15062 if (buttons[i].node === node) {
15063 return buttons;
15064 }
15065
15066 if (buttons[i].buttons.length) {
15067 var ret = this._nodeToHost(node, buttons[i].buttons);
15068
15069 if (ret) {
15070 return ret;
15071 }
15072 }
15073 }
15074 },
15075
15076 /**
15077 * Handle a key press - determine if any button's key configured matches
15078 * what was typed and trigger the action if so.
15079 * @param {string} character The character pressed
15080 * @param {object} e Key event that triggered this call
15081 * @private
15082 */
15083 _keypress: function (character, e) {
15084 // Check if this button press already activated on another instance of Buttons
15085 if (e._buttonsHandled) {
15086 return;
15087 }
15088
15089 var run = function (conf, node) {
15090 if (!conf.key) {
15091 return;
15092 }
15093
15094 if (conf.key === character) {
15095 e._buttonsHandled = true;
15096 $(node).click();
15097 }
15098 else if ($.isPlainObject(conf.key)) {
15099 if (conf.key.key !== character) {
15100 return;
15101 }
15102
15103 if (conf.key.shiftKey && !e.shiftKey) {
15104 return;
15105 }
15106
15107 if (conf.key.altKey && !e.altKey) {
15108 return;
15109 }
15110
15111 if (conf.key.ctrlKey && !e.ctrlKey) {
15112 return;
15113 }
15114
15115 if (conf.key.metaKey && !e.metaKey) {
15116 return;
15117 }
15118
15119 // Made it this far - it is good
15120 e._buttonsHandled = true;
15121 $(node).click();
15122 }
15123 };
15124
15125 var recurse = function (a) {
15126 for (var i = 0, ien = a.length; i < ien; i++) {
15127 run(a[i].conf, a[i].node);
15128
15129 if (a[i].buttons.length) {
15130 recurse(a[i].buttons);
15131 }
15132 }
15133 };
15134
15135 recurse(this.s.buttons);
15136 },
15137
15138 /**
15139 * Remove a key from the key listener for this instance (to be used when a
15140 * button is removed)
15141 * @param {object} conf Button configuration
15142 * @private
15143 */
15144 _removeKey: function (conf) {
15145 if (conf.key) {
15146 var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key;
15147
15148 // Remove only one character, as multiple buttons could have the
15149 // same listening key
15150 var a = this.s.listenKeys.split('');
15151 var idx = $.inArray(character, a);
15152 a.splice(idx, 1);
15153 this.s.listenKeys = a.join('');
15154 }
15155 },
15156
15157 /**
15158 * Resolve a button configuration
15159 * @param {string|function|object} conf Button config to resolve
15160 * @return {object} Button configuration
15161 * @private
15162 */
15163 _resolveExtends: function (conf) {
15164 var that = this;
15165 var dt = this.s.dt;
15166 var i, ien;
15167 var toConfObject = function (base) {
15168 var loop = 0;
15169
15170 // Loop until we have resolved to a button configuration, or an
15171 // array of button configurations (which will be iterated
15172 // separately)
15173 while (!$.isPlainObject(base) && !Array.isArray(base)) {
15174 if (base === undefined) {
15175 return;
15176 }
15177
15178 if (typeof base === 'function') {
15179 base = base.call(that, dt, conf);
15180
15181 if (!base) {
15182 return false;
15183 }
15184 }
15185 else if (typeof base === 'string') {
15186 if (!_dtButtons[base]) {
15187 return { html: base };
15188 }
15189
15190 base = _dtButtons[base];
15191 }
15192
15193 loop++;
15194 if (loop > 30) {
15195 // Protect against misconfiguration killing the browser
15196 throw 'Buttons: Too many iterations';
15197 }
15198 }
15199
15200 return Array.isArray(base) ? base : $.extend({}, base);
15201 };
15202
15203 conf = toConfObject(conf);
15204
15205 while (conf && conf.extend) {
15206 // Use `toConfObject` in case the button definition being extended
15207 // is itself a string or a function
15208 if (!_dtButtons[conf.extend]) {
15209 throw 'Cannot extend unknown button type: ' + conf.extend;
15210 }
15211
15212 var objArray = toConfObject(_dtButtons[conf.extend]);
15213 if (Array.isArray(objArray)) {
15214 return objArray;
15215 }
15216 else if (!objArray) {
15217 // This is a little brutal as it might be possible to have a
15218 // valid button without the extend, but if there is no extend
15219 // then the host button would be acting in an undefined state
15220 return false;
15221 }
15222
15223 // Stash the current class name
15224 var originalClassName = objArray.className;
15225
15226 if (conf.config !== undefined && objArray.config !== undefined) {
15227 conf.config = $.extend({}, objArray.config, conf.config);
15228 }
15229
15230 conf = $.extend({}, objArray, conf);
15231
15232 // The extend will have overwritten the original class name if the
15233 // `conf` object also assigned a class, but we want to concatenate
15234 // them so they are list that is combined from all extended buttons
15235 if (originalClassName && conf.className !== originalClassName) {
15236 conf.className = originalClassName + ' ' + conf.className;
15237 }
15238
15239 // Although we want the `conf` object to overwrite almost all of
15240 // the properties of the object being extended, the `extend`
15241 // property should come from the object being extended
15242 conf.extend = objArray.extend;
15243 }
15244
15245 // Buttons to be added to a collection -gives the ability to define
15246 // if buttons should be added to the start or end of a collection
15247 var postfixButtons = conf.postfixButtons;
15248 if (postfixButtons) {
15249 if (!conf.buttons) {
15250 conf.buttons = [];
15251 }
15252
15253 for (i = 0, ien = postfixButtons.length; i < ien; i++) {
15254 conf.buttons.push(postfixButtons[i]);
15255 }
15256 }
15257
15258 var prefixButtons = conf.prefixButtons;
15259 if (prefixButtons) {
15260 if (!conf.buttons) {
15261 conf.buttons = [];
15262 }
15263
15264 for (i = 0, ien = prefixButtons.length; i < ien; i++) {
15265 conf.buttons.splice(i, 0, prefixButtons[i]);
15266 }
15267 }
15268
15269 return conf;
15270 },
15271
15272 /**
15273 * Display (and replace if there is an existing one) a popover attached to a button
15274 * @param {string|node} content Content to show
15275 * @param {DataTable.Api} hostButton DT API instance of the button
15276 * @param {object} inOpts Options (see object below for all options)
15277 */
15278 _popover: function (content, hostButton, inOpts) {
15279 var dt = hostButton;
15280 var c = this.c;
15281 var closed = false;
15282 var options = $.extend(
15283 {
15284 align: 'button-left', // button-right, dt-container, split-left, split-right
15285 autoClose: false,
15286 background: true,
15287 backgroundClassName: 'dt-button-background',
15288 closeButton: true,
15289 containerClassName: c.dom.collection.container.className,
15290 contentClassName: c.dom.collection.container.content.className,
15291 collectionLayout: '',
15292 collectionTitle: '',
15293 dropup: false,
15294 fade: 400,
15295 popoverTitle: '',
15296 rightAlignClassName: 'dt-button-right',
15297 tag: c.dom.collection.container.tag
15298 },
15299 inOpts
15300 );
15301
15302 var containerSelector =
15303 options.tag + '.' + options.containerClassName.replace(/ /g, '.');
15304 var hostButtonNode = hostButton.node();
15305 var hostNode = options.collectionLayout.includes('fixed') ? $('body') : hostButton.node();
15306
15307 var close = function () {
15308 closed = true;
15309
15310 _fadeOut($(containerSelector), options.fade, function () {
15311 $(this).detach();
15312 });
15313
15314 $(
15315 dt
15316 .buttons('[aria-haspopup="dialog"][aria-expanded="true"]')
15317 .nodes()
15318 ).attr('aria-expanded', 'false');
15319
15320 $('div.dt-button-background').off('click.dtb-collection');
15321 Buttons.background(
15322 false,
15323 options.backgroundClassName,
15324 options.fade,
15325 hostNode
15326 );
15327
15328 $(window).off('resize.resize.dtb-collection');
15329 $('body').off('.dtb-collection');
15330 dt.off('buttons-action.b-internal');
15331 dt.off('destroy');
15332
15333 $('body').trigger('buttons-popover-hide.dt');
15334 };
15335
15336 if (content === false) {
15337 close();
15338 return;
15339 }
15340
15341 var existingExpanded = $(
15342 dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()
15343 );
15344 if (existingExpanded.length) {
15345 // Reuse the current position if the button that was triggered is inside an existing collection
15346 if (hostNode.closest(containerSelector).length) {
15347 hostNode = existingExpanded.eq(0);
15348 }
15349
15350 close();
15351 }
15352
15353 // Sort buttons if defined
15354 if (options.sort) {
15355 var elements = $('button', content)
15356 .map(function (idx, el) {
15357 return {
15358 text: $(el).text(),
15359 el: el
15360 };
15361 })
15362 .toArray();
15363
15364 elements.sort(function (a, b) {
15365 return a.text.localeCompare(b.text);
15366 });
15367
15368 $(content).append(elements.map(function (v) {
15369 return v.el;
15370 }));
15371 }
15372
15373 // Try to be smart about the layout
15374 var cnt = $('.dt-button', content).length;
15375 var mod = '';
15376
15377 if (cnt === 3) {
15378 mod = 'dtb-b3';
15379 }
15380 else if (cnt === 2) {
15381 mod = 'dtb-b2';
15382 }
15383 else if (cnt === 1) {
15384 mod = 'dtb-b1';
15385 }
15386
15387 var display = $('<' + options.tag + '/>')
15388 .addClass(options.containerClassName)
15389 .addClass(options.collectionLayout)
15390 .addClass(options.splitAlignClass)
15391 .addClass(mod)
15392 .css('display', 'none')
15393 .attr({
15394 'aria-modal': true,
15395 role: 'dialog'
15396 });
15397
15398 content = $(content)
15399 .addClass(options.contentClassName)
15400 .attr('role', 'menu')
15401 .appendTo(display);
15402
15403 hostButtonNode.attr('aria-expanded', 'true');
15404
15405 if (hostNode.parents('body')[0] !== document.body) {
15406 hostNode = $(document.body).children('div, section, p').last();
15407 }
15408
15409 if (options.popoverTitle) {
15410 display.prepend(
15411 '<div class="dt-button-collection-title">' +
15412 options.popoverTitle +
15413 '</div>'
15414 );
15415 }
15416 else if (options.collectionTitle) {
15417 display.prepend(
15418 '<div class="dt-button-collection-title">' +
15419 options.collectionTitle +
15420 '</div>'
15421 );
15422 }
15423
15424 if (options.closeButton) {
15425 display
15426 .prepend('<div class="dtb-popover-close">&times;</div>')
15427 .addClass('dtb-collection-closeable');
15428 }
15429
15430 _fadeIn(display.insertAfter(hostNode), options.fade);
15431
15432 var tableContainer = $(hostButton.table().container());
15433 var position = display.css('position');
15434
15435 if (options.span === 'container' || options.align === 'dt-container') {
15436 hostNode = hostNode.parent();
15437 display.css('width', tableContainer.width());
15438 }
15439
15440 // Align the popover relative to the DataTables container
15441 // Useful for wide popovers such as SearchPanes
15442 if (position === 'absolute') {
15443 // Align relative to the host button
15444 var offsetParent = $(hostNode[0].offsetParent);
15445 var buttonPosition = hostNode.position();
15446 var buttonOffset = hostNode.offset();
15447 var tableSizes = offsetParent.offset();
15448 var containerPosition = offsetParent.position();
15449 var computed = window.getComputedStyle(offsetParent[0]);
15450
15451 tableSizes.height = offsetParent.outerHeight();
15452 tableSizes.width =
15453 offsetParent.width() + parseFloat(computed.paddingLeft);
15454 tableSizes.right = tableSizes.left + tableSizes.width;
15455 tableSizes.bottom = tableSizes.top + tableSizes.height;
15456
15457 // Set the initial position so we can read height / width
15458 var top = buttonPosition.top + hostNode.outerHeight();
15459 var left = buttonPosition.left;
15460
15461 display.css({
15462 top: top,
15463 left: left
15464 });
15465
15466 // Get the popover position
15467 computed = window.getComputedStyle(display[0]);
15468 var popoverSizes = display.offset();
15469
15470 popoverSizes.height = display.outerHeight();
15471 popoverSizes.width = display.outerWidth();
15472 popoverSizes.right = popoverSizes.left + popoverSizes.width;
15473 popoverSizes.bottom = popoverSizes.top + popoverSizes.height;
15474 popoverSizes.marginTop = parseFloat(computed.marginTop);
15475 popoverSizes.marginBottom = parseFloat(computed.marginBottom);
15476
15477 // First position per the class requirements - pop up and right align
15478 if (options.dropup) {
15479 top =
15480 buttonPosition.top -
15481 popoverSizes.height -
15482 popoverSizes.marginTop -
15483 popoverSizes.marginBottom;
15484 }
15485
15486 if (
15487 options.align === 'button-right' ||
15488 display.hasClass(options.rightAlignClassName)
15489 ) {
15490 left =
15491 buttonPosition.left -
15492 popoverSizes.width +
15493 hostNode.outerWidth();
15494 }
15495
15496 // Container alignment - make sure it doesn't overflow the table container
15497 if (
15498 options.align === 'dt-container' ||
15499 options.align === 'container'
15500 ) {
15501 if (left < buttonPosition.left) {
15502 left = -buttonPosition.left;
15503 }
15504 }
15505
15506 // Window adjustment
15507 if (
15508 containerPosition.left + left + popoverSizes.width >
15509 $(window).width()
15510 ) {
15511 // Overflowing the document to the right
15512 left =
15513 $(window).width() -
15514 popoverSizes.width -
15515 containerPosition.left;
15516 }
15517
15518 if (buttonOffset.left + left < 0) {
15519 // Off to the left of the document
15520 left = -buttonOffset.left;
15521 }
15522
15523 if (
15524 containerPosition.top + top + popoverSizes.height >
15525 $(window).height() + $(window).scrollTop()
15526 ) {
15527 // Pop up if otherwise we'd need the user to scroll down
15528 top =
15529 buttonPosition.top -
15530 popoverSizes.height -
15531 popoverSizes.marginTop -
15532 popoverSizes.marginBottom;
15533 }
15534
15535 if (offsetParent.offset().top + top < $(window).scrollTop()) {
15536 // Correction for when the top is beyond the top of the page
15537 top = buttonPosition.top + hostNode.outerHeight();
15538 }
15539
15540 // Calculations all done - now set it
15541 display.css({
15542 top: top,
15543 left: left
15544 });
15545 }
15546 else {
15547 // Fix position - centre on screen
15548 var place = function () {
15549 var half = $(window).height() / 2;
15550
15551 var top = display.height() / 2;
15552 if (top > half) {
15553 top = half;
15554 }
15555
15556 display.css('marginTop', top * -1);
15557 };
15558
15559 place();
15560
15561 $(window).on('resize.dtb-collection', function () {
15562 place();
15563 });
15564 }
15565
15566 if (options.background) {
15567 Buttons.background(
15568 true,
15569 options.backgroundClassName,
15570 options.fade,
15571 options.backgroundHost || hostNode
15572 );
15573 }
15574
15575 // This is bonkers, but if we don't have a click listener on the
15576 // background element, iOS Safari will ignore the body click
15577 // listener below. An empty function here is all that is
15578 // required to make it work...
15579 $('div.dt-button-background').on(
15580 'click.dtb-collection',
15581 function () {}
15582 );
15583
15584 if (options.autoClose) {
15585 setTimeout(function () {
15586 dt.on('buttons-action.b-internal', function (e, btn, dt, node) {
15587 if (node[0] === hostNode[0]) {
15588 return;
15589 }
15590 close();
15591 });
15592 }, 0);
15593 }
15594
15595 $(display).trigger('buttons-popover.dt');
15596
15597 dt.on('destroy', close);
15598
15599 setTimeout(function () {
15600 closed = false;
15601 $('body')
15602 .on('click.dtb-collection', function (e) {
15603 if (closed) {
15604 return;
15605 }
15606
15607 // andSelf is deprecated in jQ1.8, but we want 1.7 compat
15608 var back = $.fn.addBack ? 'addBack' : 'andSelf';
15609 var parent = $(e.target).parent()[0];
15610
15611 if (
15612 (!$(e.target).parents()[back]().filter(content)
15613 .length &&
15614 !$(parent).hasClass('dt-buttons')) ||
15615 $(e.target).hasClass('dt-button-background')
15616 ) {
15617 close();
15618 }
15619 })
15620 .on('keyup.dtb-collection', function (e) {
15621 if (e.keyCode === 27) {
15622 close();
15623 }
15624 })
15625 .on('keydown.dtb-collection', function (e) {
15626 // Focus trap for tab key
15627 var elements = $('a, button', content);
15628 var active = document.activeElement;
15629
15630 if (e.keyCode !== 9) {
15631 // tab
15632 return;
15633 }
15634
15635 if (elements.index(active) === -1) {
15636 // If current focus is not inside the popover
15637 elements.first().focus();
15638 e.preventDefault();
15639 }
15640 else if (e.shiftKey) {
15641 // Reverse tabbing order when shift key is pressed
15642 if (active === elements[0]) {
15643 elements.last().focus();
15644 e.preventDefault();
15645 }
15646 }
15647 else {
15648 if (active === elements.last()[0]) {
15649 elements.first().focus();
15650 e.preventDefault();
15651 }
15652 }
15653 });
15654 }, 0);
15655 }
15656 });
15657
15658 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
15659 * Statics
15660 */
15661
15662 /**
15663 * Show / hide a background layer behind a collection
15664 * @param {boolean} Flag to indicate if the background should be shown or
15665 * hidden
15666 * @param {string} Class to assign to the background
15667 * @static
15668 */
15669 Buttons.background = function (show, className, fade, insertPoint) {
15670 if (fade === undefined) {
15671 fade = 400;
15672 }
15673 if (!insertPoint) {
15674 insertPoint = document.body;
15675 }
15676
15677 if (show) {
15678 _fadeIn(
15679 $('<div/>')
15680 .addClass(className)
15681 .css('display', 'none')
15682 .insertAfter(insertPoint),
15683 fade
15684 );
15685 }
15686 else {
15687 _fadeOut($('div.' + className), fade, function () {
15688 $(this).removeClass(className).remove();
15689 });
15690 }
15691 };
15692
15693 /**
15694 * Instance selector - select Buttons instances based on an instance selector
15695 * value from the buttons assigned to a DataTable. This is only useful if
15696 * multiple instances are attached to a DataTable.
15697 * @param {string|int|array} Instance selector - see `instance-selector`
15698 * documentation on the DataTables site
15699 * @param {array} Button instance array that was attached to the DataTables
15700 * settings object
15701 * @return {array} Buttons instances
15702 * @static
15703 */
15704 Buttons.instanceSelector = function (group, buttons) {
15705 if (group === undefined || group === null) {
15706 return $.map(buttons, function (v) {
15707 return v.inst;
15708 });
15709 }
15710
15711 var ret = [];
15712 var names = $.map(buttons, function (v) {
15713 return v.name;
15714 });
15715
15716 // Flatten the group selector into an array of single options
15717 var process = function (input) {
15718 if (Array.isArray(input)) {
15719 for (var i = 0, ien = input.length; i < ien; i++) {
15720 process(input[i]);
15721 }
15722 return;
15723 }
15724
15725 if (typeof input === 'string') {
15726 if (input.indexOf(',') !== -1) {
15727 // String selector, list of names
15728 process(input.split(','));
15729 }
15730 else {
15731 // String selector individual name
15732 var idx = $.inArray(input.trim(), names);
15733
15734 if (idx !== -1) {
15735 ret.push(buttons[idx].inst);
15736 }
15737 }
15738 }
15739 else if (typeof input === 'number') {
15740 // Index selector
15741 ret.push(buttons[input].inst);
15742 }
15743 else if (typeof input === 'object' && input.nodeName) {
15744 // Element selector
15745 for (var j = 0; j < buttons.length; j++) {
15746 if (buttons[j].inst.dom.container[0] === input) {
15747 ret.push(buttons[j].inst);
15748 }
15749 }
15750 }
15751 else if (typeof input === 'object') {
15752 // Actual instance selector
15753 ret.push(input);
15754 }
15755 };
15756
15757 process(group);
15758
15759 return ret;
15760 };
15761
15762 /**
15763 * Button selector - select one or more buttons from a selector input so some
15764 * operation can be performed on them.
15765 * @param {array} Button instances array that the selector should operate on
15766 * @param {string|int|node|jQuery|array} Button selector - see
15767 * `button-selector` documentation on the DataTables site
15768 * @return {array} Array of objects containing `inst` and `idx` properties of
15769 * the selected buttons so you know which instance each button belongs to.
15770 * @static
15771 */
15772 Buttons.buttonSelector = function (insts, selector) {
15773 var ret = [];
15774 var nodeBuilder = function (a, buttons, baseIdx) {
15775 var button;
15776 var idx;
15777
15778 for (var i = 0, ien = buttons.length; i < ien; i++) {
15779 button = buttons[i];
15780
15781 if (button) {
15782 idx = baseIdx !== undefined ? baseIdx + i : i + '';
15783
15784 a.push({
15785 node: button.node,
15786 name: button.conf.name,
15787 idx: idx
15788 });
15789
15790 if (button.buttons) {
15791 nodeBuilder(a, button.buttons, idx + '-');
15792 }
15793 }
15794 }
15795 };
15796
15797 var run = function (selector, inst) {
15798 var i, ien;
15799 var buttons = [];
15800 nodeBuilder(buttons, inst.s.buttons);
15801
15802 var nodes = $.map(buttons, function (v) {
15803 return v.node;
15804 });
15805
15806 if (Array.isArray(selector) || selector instanceof $) {
15807 for (i = 0, ien = selector.length; i < ien; i++) {
15808 run(selector[i], inst);
15809 }
15810 return;
15811 }
15812
15813 if (selector === null || selector === undefined || selector === '*') {
15814 // Select all
15815 for (i = 0, ien = buttons.length; i < ien; i++) {
15816 ret.push({
15817 inst: inst,
15818 node: buttons[i].node
15819 });
15820 }
15821 }
15822 else if (typeof selector === 'number') {
15823 // Main button index selector
15824 if (inst.s.buttons[selector]) {
15825 ret.push({
15826 inst: inst,
15827 node: inst.s.buttons[selector].node
15828 });
15829 }
15830 }
15831 else if (typeof selector === 'string') {
15832 if (selector.indexOf(',') !== -1) {
15833 // Split
15834 var a = selector.split(',');
15835
15836 for (i = 0, ien = a.length; i < ien; i++) {
15837 run(a[i].trim(), inst);
15838 }
15839 }
15840 else if (selector.match(/^\d+(\-\d+)*$/)) {
15841 // Sub-button index selector
15842 var indexes = $.map(buttons, function (v) {
15843 return v.idx;
15844 });
15845
15846 ret.push({
15847 inst: inst,
15848 node: buttons[$.inArray(selector, indexes)].node
15849 });
15850 }
15851 else if (selector.indexOf(':name') !== -1) {
15852 // Button name selector
15853 var name = selector.replace(':name', '');
15854
15855 for (i = 0, ien = buttons.length; i < ien; i++) {
15856 if (buttons[i].name === name) {
15857 ret.push({
15858 inst: inst,
15859 node: buttons[i].node
15860 });
15861 }
15862 }
15863 }
15864 else {
15865 // jQuery selector on the nodes
15866 $(nodes)
15867 .filter(selector)
15868 .each(function () {
15869 ret.push({
15870 inst: inst,
15871 node: this
15872 });
15873 });
15874 }
15875 }
15876 else if (typeof selector === 'object' && selector.nodeName) {
15877 // Node selector
15878 var idx = $.inArray(selector, nodes);
15879
15880 if (idx !== -1) {
15881 ret.push({
15882 inst: inst,
15883 node: nodes[idx]
15884 });
15885 }
15886 }
15887 };
15888
15889 for (var i = 0, ien = insts.length; i < ien; i++) {
15890 var inst = insts[i];
15891
15892 run(selector, inst);
15893 }
15894
15895 return ret;
15896 };
15897
15898 /**
15899 * Default function used for formatting output data.
15900 * @param {*} str Data to strip
15901 */
15902 Buttons.stripData = function (str, config) {
15903 // If the input is an HTML element, we can use the HTML from it (HTML might be stripped below).
15904 if (str !== null && typeof str === 'object' && str.nodeName && str.nodeType) {
15905 str = str.innerHTML;
15906 }
15907
15908 if (typeof str !== 'string') {
15909 return str;
15910 }
15911
15912 // Always remove script tags
15913 str = Buttons.stripHtmlScript(str);
15914
15915 // Always remove comments
15916 str = Buttons.stripHtmlComments(str);
15917
15918 if (!config || config.stripHtml) {
15919 str = DataTable.util.stripHtml(str);
15920 }
15921
15922 if (!config || config.trim) {
15923 str = str.trim();
15924 }
15925
15926 if (!config || config.stripNewlines) {
15927 str = str.replace(/\n/g, ' ');
15928 }
15929
15930 if (!config || config.decodeEntities) {
15931 if (_entityDecoder) {
15932 str = _entityDecoder(str);
15933 }
15934 else {
15935 _exportTextarea.innerHTML = str;
15936 str = _exportTextarea.value;
15937 }
15938 }
15939
15940 // Prevent Excel from running a formula
15941 if (!config || config.escapeExcelFormula) {
15942 if (str.match(/^[=+\-@\t\r]/)) {
15943 console.log('matching and updateing');
15944 str = "'" + str;
15945 }
15946 }
15947
15948 return str;
15949 };
15950
15951 /**
15952 * Provide a custom entity decoding function - e.g. a regex one, which can be
15953 * much faster than the built in DOM option, but also larger code size.
15954 * @param {function} fn
15955 */
15956 Buttons.entityDecoder = function (fn) {
15957 _entityDecoder = fn;
15958 };
15959
15960 /**
15961 * Common function for stripping HTML comments
15962 *
15963 * @param {*} input
15964 * @returns
15965 */
15966 Buttons.stripHtmlComments = function (input) {
15967 var previous;
15968
15969 do {
15970 previous = input;
15971 input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, '');
15972 } while (input !== previous);
15973
15974 return input;
15975 };
15976
15977 /**
15978 * Common function for stripping HTML script tags
15979 *
15980 * @param {*} input
15981 * @returns
15982 */
15983 Buttons.stripHtmlScript = function (input) {
15984 var previous;
15985
15986 do {
15987 previous = input;
15988 input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, '');
15989 } while (input !== previous);
15990
15991 return input;
15992 };
15993
15994 /**
15995 * Buttons defaults. For full documentation, please refer to the docs/option
15996 * directory or the DataTables site.
15997 * @type {Object}
15998 * @static
15999 */
16000 Buttons.defaults = {
16001 buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
16002 name: 'main',
16003 tabIndex: 0,
16004 dom: {
16005 container: {
16006 tag: 'div',
16007 className: 'dt-buttons'
16008 },
16009 collection: {
16010 container: {
16011 // The element used for the dropdown
16012 className: 'dt-button-collection',
16013 content: {
16014 className: '',
16015 tag: 'div'
16016 },
16017 tag: 'div'
16018 }
16019 // optionally
16020 // , button: IButton - buttons inside the collection container
16021 // , split: ISplit - splits inside the collection container
16022 },
16023 button: {
16024 tag: 'button',
16025 className: 'dt-button',
16026 active: 'dt-button-active', // class name
16027 disabled: 'disabled', // class name
16028 spacer: {
16029 className: 'dt-button-spacer',
16030 tag: 'span'
16031 },
16032 liner: {
16033 tag: 'span',
16034 className: ''
16035 },
16036 dropClass: '',
16037 dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>'
16038 },
16039 split: {
16040 action: {
16041 // action button
16042 className: 'dt-button-split-drop-button dt-button',
16043 tag: 'button'
16044 },
16045 dropdown: {
16046 // button to trigger the dropdown
16047 align: 'split-right',
16048 className: 'dt-button-split-drop',
16049 splitAlignClass: 'dt-button-split-left',
16050 tag: 'button'
16051 },
16052 wrapper: {
16053 // wrap around both
16054 className: 'dt-button-split',
16055 tag: 'div'
16056 }
16057 }
16058 }
16059 };
16060
16061 /**
16062 * Version information
16063 * @type {string}
16064 * @static
16065 */
16066 Buttons.version = '3.2.2';
16067
16068 $.extend(_dtButtons, {
16069 collection: {
16070 text: function (dt) {
16071 return dt.i18n('buttons.collection', 'Collection');
16072 },
16073 className: 'buttons-collection',
16074 closeButton: false,
16075 dropIcon: true,
16076 init: function (dt, button) {
16077 button.attr('aria-expanded', false);
16078 },
16079 action: function (e, dt, button, config) {
16080 if (config._collection.parents('body').length) {
16081 this.popover(false, config);
16082 }
16083 else {
16084 this.popover(config._collection, config);
16085 }
16086
16087 // When activated using a key - auto focus on the
16088 // first item in the popover
16089 if (e.type === 'keypress') {
16090 $('a, button', config._collection).eq(0).focus();
16091 }
16092 },
16093 attr: {
16094 'aria-haspopup': 'dialog'
16095 }
16096 // Also the popover options, defined in Buttons.popover
16097 },
16098 split: {
16099 text: function (dt) {
16100 return dt.i18n('buttons.split', 'Split');
16101 },
16102 className: 'buttons-split',
16103 closeButton: false,
16104 init: function (dt, button) {
16105 return button.attr('aria-expanded', false);
16106 },
16107 action: function (e, dt, button, config) {
16108 this.popover(config._collection, config);
16109 },
16110 attr: {
16111 'aria-haspopup': 'dialog'
16112 }
16113 // Also the popover options, defined in Buttons.popover
16114 },
16115 copy: function () {
16116 if (_dtButtons.copyHtml5) {
16117 return 'copyHtml5';
16118 }
16119 },
16120 csv: function (dt, conf) {
16121 if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
16122 return 'csvHtml5';
16123 }
16124 },
16125 excel: function (dt, conf) {
16126 if (
16127 _dtButtons.excelHtml5 &&
16128 _dtButtons.excelHtml5.available(dt, conf)
16129 ) {
16130 return 'excelHtml5';
16131 }
16132 },
16133 pdf: function (dt, conf) {
16134 if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
16135 return 'pdfHtml5';
16136 }
16137 },
16138 pageLength: function (dt) {
16139 var lengthMenu = dt.settings()[0].aLengthMenu;
16140 var vals = [];
16141 var lang = [];
16142 var text = function (dt) {
16143 return dt.i18n(
16144 'buttons.pageLength',
16145 {
16146 '-1': 'Show all rows',
16147 _: 'Show %d rows'
16148 },
16149 dt.page.len()
16150 );
16151 };
16152
16153 // Support for DataTables 1.x 2D array
16154 if (Array.isArray(lengthMenu[0])) {
16155 vals = lengthMenu[0];
16156 lang = lengthMenu[1];
16157 }
16158 else {
16159 for (var i = 0; i < lengthMenu.length; i++) {
16160 var option = lengthMenu[i];
16161
16162 // Support for DataTables 2 object in the array
16163 if ($.isPlainObject(option)) {
16164 vals.push(option.value);
16165 lang.push(option.label);
16166 }
16167 else {
16168 vals.push(option);
16169 lang.push(option);
16170 }
16171 }
16172 }
16173
16174 return {
16175 extend: 'collection',
16176 text: text,
16177 className: 'buttons-page-length',
16178 autoClose: true,
16179 buttons: $.map(vals, function (val, i) {
16180 return {
16181 text: lang[i],
16182 className: 'button-page-length',
16183 action: function (e, dt) {
16184 dt.page.len(val).draw();
16185 },
16186 init: function (dt, node, conf) {
16187 var that = this;
16188 var fn = function () {
16189 that.active(dt.page.len() === val);
16190 };
16191
16192 dt.on('length.dt' + conf.namespace, fn);
16193 fn();
16194 },
16195 destroy: function (dt, node, conf) {
16196 dt.off('length.dt' + conf.namespace);
16197 }
16198 };
16199 }),
16200 init: function (dt, node, conf) {
16201 var that = this;
16202 dt.on('length.dt' + conf.namespace, function () {
16203 that.text(conf.text);
16204 });
16205 },
16206 destroy: function (dt, node, conf) {
16207 dt.off('length.dt' + conf.namespace);
16208 }
16209 };
16210 },
16211 spacer: {
16212 style: 'empty',
16213 spacer: true,
16214 text: function (dt) {
16215 return dt.i18n('buttons.spacer', '');
16216 }
16217 }
16218 });
16219
16220 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16221 * DataTables API
16222 *
16223 * For complete documentation, please refer to the docs/api directory or the
16224 * DataTables site
16225 */
16226
16227 // Buttons group and individual button selector
16228 DataTable.Api.register('buttons()', function (group, selector) {
16229 // Argument shifting
16230 if (selector === undefined) {
16231 selector = group;
16232 group = undefined;
16233 }
16234
16235 this.selector.buttonGroup = group;
16236
16237 var res = this.iterator(
16238 true,
16239 'table',
16240 function (ctx) {
16241 if (ctx._buttons) {
16242 return Buttons.buttonSelector(
16243 Buttons.instanceSelector(group, ctx._buttons),
16244 selector
16245 );
16246 }
16247 },
16248 true
16249 );
16250
16251 res._groupSelector = group;
16252 return res;
16253 });
16254
16255 // Individual button selector
16256 DataTable.Api.register('button()', function (group, selector) {
16257 // just run buttons() and truncate
16258 var buttons = this.buttons(group, selector);
16259
16260 if (buttons.length > 1) {
16261 buttons.splice(1, buttons.length);
16262 }
16263
16264 return buttons;
16265 });
16266
16267 // Active buttons
16268 DataTable.Api.registerPlural(
16269 'buttons().active()',
16270 'button().active()',
16271 function (flag) {
16272 if (flag === undefined) {
16273 return this.map(function (set) {
16274 return set.inst.active(set.node);
16275 });
16276 }
16277
16278 return this.each(function (set) {
16279 set.inst.active(set.node, flag);
16280 });
16281 }
16282 );
16283
16284 // Get / set button action
16285 DataTable.Api.registerPlural(
16286 'buttons().action()',
16287 'button().action()',
16288 function (action) {
16289 if (action === undefined) {
16290 return this.map(function (set) {
16291 return set.inst.action(set.node);
16292 });
16293 }
16294
16295 return this.each(function (set) {
16296 set.inst.action(set.node, action);
16297 });
16298 }
16299 );
16300
16301 // Collection control
16302 DataTable.Api.registerPlural(
16303 'buttons().collectionRebuild()',
16304 'button().collectionRebuild()',
16305 function (buttons) {
16306 return this.each(function (set) {
16307 for (var i = 0; i < buttons.length; i++) {
16308 if (typeof buttons[i] === 'object') {
16309 buttons[i].parentConf = set;
16310 }
16311 }
16312 set.inst.collectionRebuild(set.node, buttons);
16313 });
16314 }
16315 );
16316
16317 // Enable / disable buttons
16318 DataTable.Api.register(
16319 ['buttons().enable()', 'button().enable()'],
16320 function (flag) {
16321 return this.each(function (set) {
16322 set.inst.enable(set.node, flag);
16323 });
16324 }
16325 );
16326
16327 // Disable buttons
16328 DataTable.Api.register(
16329 ['buttons().disable()', 'button().disable()'],
16330 function () {
16331 return this.each(function (set) {
16332 set.inst.disable(set.node);
16333 });
16334 }
16335 );
16336
16337 // Button index
16338 DataTable.Api.register('button().index()', function () {
16339 var idx = null;
16340
16341 this.each(function (set) {
16342 var res = set.inst.index(set.node);
16343
16344 if (res !== null) {
16345 idx = res;
16346 }
16347 });
16348
16349 return idx;
16350 });
16351
16352 // Get button nodes
16353 DataTable.Api.registerPlural(
16354 'buttons().nodes()',
16355 'button().node()',
16356 function () {
16357 var jq = $();
16358
16359 // jQuery will automatically reduce duplicates to a single entry
16360 $(
16361 this.each(function (set) {
16362 jq = jq.add(set.inst.node(set.node));
16363 })
16364 );
16365
16366 return jq;
16367 }
16368 );
16369
16370 // Get / set button processing state
16371 DataTable.Api.registerPlural(
16372 'buttons().processing()',
16373 'button().processing()',
16374 function (flag) {
16375 if (flag === undefined) {
16376 return this.map(function (set) {
16377 return set.inst.processing(set.node);
16378 });
16379 }
16380
16381 return this.each(function (set) {
16382 set.inst.processing(set.node, flag);
16383 });
16384 }
16385 );
16386
16387 // Get / set button text (i.e. the button labels)
16388 DataTable.Api.registerPlural(
16389 'buttons().text()',
16390 'button().text()',
16391 function (label) {
16392 if (label === undefined) {
16393 return this.map(function (set) {
16394 return set.inst.text(set.node);
16395 });
16396 }
16397
16398 return this.each(function (set) {
16399 set.inst.text(set.node, label);
16400 });
16401 }
16402 );
16403
16404 // Trigger a button's action
16405 DataTable.Api.registerPlural(
16406 'buttons().trigger()',
16407 'button().trigger()',
16408 function () {
16409 return this.each(function (set) {
16410 set.inst.node(set.node).trigger('click');
16411 });
16412 }
16413 );
16414
16415 // Button resolver to the popover
16416 DataTable.Api.register('button().popover()', function (content, options) {
16417 return this.map(function (set) {
16418 return set.inst._popover(content, this.button(this[0].node), options);
16419 });
16420 });
16421
16422 // Get the container elements
16423 DataTable.Api.register('buttons().containers()', function () {
16424 var jq = $();
16425 var groupSelector = this._groupSelector;
16426
16427 // We need to use the group selector directly, since if there are no buttons
16428 // the result set will be empty
16429 this.iterator(true, 'table', function (ctx) {
16430 if (ctx._buttons) {
16431 var insts = Buttons.instanceSelector(groupSelector, ctx._buttons);
16432
16433 for (var i = 0, ien = insts.length; i < ien; i++) {
16434 jq = jq.add(insts[i].container());
16435 }
16436 }
16437 });
16438
16439 return jq;
16440 });
16441
16442 DataTable.Api.register('buttons().container()', function () {
16443 // API level of nesting is `buttons()` so we can zip into the containers method
16444 return this.containers().eq(0);
16445 });
16446
16447 // Add a new button
16448 DataTable.Api.register('button().add()', function (idx, conf, draw) {
16449 var ctx = this.context;
16450
16451 // Don't use `this` as it could be empty - select the instances directly
16452 if (ctx.length) {
16453 var inst = Buttons.instanceSelector(
16454 this._groupSelector,
16455 ctx[0]._buttons
16456 );
16457
16458 if (inst.length) {
16459 inst[0].add(conf, idx, draw);
16460 }
16461 }
16462
16463 return this.button(this._groupSelector, idx);
16464 });
16465
16466 // Destroy the button sets selected
16467 DataTable.Api.register('buttons().destroy()', function () {
16468 this.pluck('inst')
16469 .unique()
16470 .each(function (inst) {
16471 inst.destroy();
16472 });
16473
16474 return this;
16475 });
16476
16477 // Remove a button
16478 DataTable.Api.registerPlural(
16479 'buttons().remove()',
16480 'buttons().remove()',
16481 function () {
16482 this.each(function (set) {
16483 set.inst.remove(set.node);
16484 });
16485
16486 return this;
16487 }
16488 );
16489
16490 // Information box that can be used by buttons
16491 var _infoTimer;
16492 DataTable.Api.register('buttons.info()', function (title, message, time) {
16493 var that = this;
16494
16495 if (title === false) {
16496 this.off('destroy.btn-info');
16497 _fadeOut($('#datatables_buttons_info'), 400, function () {
16498 $(this).remove();
16499 });
16500 clearTimeout(_infoTimer);
16501 _infoTimer = null;
16502
16503 return this;
16504 }
16505
16506 if (_infoTimer) {
16507 clearTimeout(_infoTimer);
16508 }
16509
16510 if ($('#datatables_buttons_info').length) {
16511 $('#datatables_buttons_info').remove();
16512 }
16513
16514 title = title ? '<h2>' + title + '</h2>' : '';
16515
16516 _fadeIn(
16517 $('<div id="datatables_buttons_info" class="dt-button-info"/>')
16518 .html(title)
16519 .append(
16520 $('<div/>')[typeof message === 'string' ? 'html' : 'append'](
16521 message
16522 )
16523 )
16524 .css('display', 'none')
16525 .appendTo('body')
16526 );
16527
16528 if (time !== undefined && time !== 0) {
16529 _infoTimer = setTimeout(function () {
16530 that.buttons.info(false);
16531 }, time);
16532 }
16533
16534 this.on('destroy.btn-info', function () {
16535 that.buttons.info(false);
16536 });
16537
16538 return this;
16539 });
16540
16541 // Get data from the table for export - this is common to a number of plug-in
16542 // buttons so it is included in the Buttons core library
16543 DataTable.Api.register('buttons.exportData()', function (options) {
16544 if (this.context.length) {
16545 return _exportData(new DataTable.Api(this.context[0]), options);
16546 }
16547 });
16548
16549 // Get information about the export that is common to many of the export data
16550 // types (DRY)
16551 DataTable.Api.register('buttons.exportInfo()', function (conf) {
16552 if (!conf) {
16553 conf = {};
16554 }
16555
16556 return {
16557 filename: _filename(conf, this),
16558 title: _title(conf, this),
16559 messageTop: _message(this, conf, conf.message || conf.messageTop, 'top'),
16560 messageBottom: _message(this, conf, conf.messageBottom, 'bottom')
16561 };
16562 });
16563
16564 /**
16565 * Get the file name for an exported file.
16566 *
16567 * @param {object} config Button configuration
16568 * @param {object} dt DataTable instance
16569 */
16570 var _filename = function (config, dt) {
16571 // Backwards compatibility
16572 var filename =
16573 config.filename === '*' &&
16574 config.title !== '*' &&
16575 config.title !== undefined &&
16576 config.title !== null &&
16577 config.title !== ''
16578 ? config.title
16579 : config.filename;
16580
16581 if (typeof filename === 'function') {
16582 filename = filename(config, dt);
16583 }
16584
16585 if (filename === undefined || filename === null) {
16586 return null;
16587 }
16588
16589 if (filename.indexOf('*') !== -1) {
16590 filename = filename.replace(/\*/g, $('head > title').text()).trim();
16591 }
16592
16593 // Strip characters which the OS will object to
16594 filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, '');
16595
16596 var extension = _stringOrFunction(config.extension, config, dt);
16597 if (!extension) {
16598 extension = '';
16599 }
16600
16601 return filename + extension;
16602 };
16603
16604 /**
16605 * Simply utility method to allow parameters to be given as a function
16606 *
16607 * @param {undefined|string|function} option Option
16608 * @return {null|string} Resolved value
16609 */
16610 var _stringOrFunction = function (option, config, dt) {
16611 if (option === null || option === undefined) {
16612 return null;
16613 }
16614 else if (typeof option === 'function') {
16615 return option(config, dt);
16616 }
16617 return option;
16618 };
16619
16620 /**
16621 * Get the title for an exported file.
16622 *
16623 * @param {object} config Button configuration
16624 */
16625 var _title = function (config, dt) {
16626 var title = _stringOrFunction(config.title, config, dt);
16627
16628 return title === null
16629 ? null
16630 : title.indexOf('*') !== -1
16631 ? title.replace(/\*/g, $('head > title').text() || 'Exported data')
16632 : title;
16633 };
16634
16635 var _message = function (dt, config, option, position) {
16636 var message = _stringOrFunction(option, config, dt);
16637 if (message === null) {
16638 return null;
16639 }
16640
16641 var caption = $('caption', dt.table().container()).eq(0);
16642 if (message === '*') {
16643 var side = caption.css('caption-side');
16644 if (side !== position) {
16645 return null;
16646 }
16647
16648 return caption.length ? caption.text() : '';
16649 }
16650
16651 return message;
16652 };
16653
16654 var _exportTextarea = $('<textarea/>')[0];
16655 var _exportData = function (dt, inOpts) {
16656 var config = $.extend(
16657 true,
16658 {},
16659 {
16660 rows: null,
16661 columns: '',
16662 modifier: {
16663 search: 'applied',
16664 order: 'applied'
16665 },
16666 orthogonal: 'display',
16667 stripHtml: true,
16668 stripNewlines: true,
16669 decodeEntities: true,
16670 escapeExcelFormula: false,
16671 trim: true,
16672 format: {
16673 header: function (d) {
16674 return Buttons.stripData(d, config);
16675 },
16676 footer: function (d) {
16677 return Buttons.stripData(d, config);
16678 },
16679 body: function (d) {
16680 return Buttons.stripData(d, config);
16681 }
16682 },
16683 customizeData: null,
16684 customizeZip: null
16685 },
16686 inOpts
16687 );
16688
16689 var header = dt
16690 .columns(config.columns)
16691 .indexes()
16692 .map(function (idx) {
16693 var col = dt.column(idx);
16694 return config.format.header(col.title(), idx, col.header());
16695 })
16696 .toArray();
16697
16698 var footer = dt.table().footer()
16699 ? dt
16700 .columns(config.columns)
16701 .indexes()
16702 .map(function (idx) {
16703 var el = dt.column(idx).footer();
16704 var val = '';
16705
16706 if (el) {
16707 var inner = $('.dt-column-title', el);
16708
16709 val = inner.length
16710 ? inner.html()
16711 : $(el).html();
16712 }
16713
16714 return config.format.footer(val, idx, el);
16715 })
16716 .toArray()
16717 : null;
16718
16719 // If Select is available on this table, and any rows are selected, limit the export
16720 // to the selected rows. If no rows are selected, all rows will be exported. Specify
16721 // a `selected` modifier to control directly.
16722 var modifier = $.extend({}, config.modifier);
16723 if (
16724 dt.select &&
16725 typeof dt.select.info === 'function' &&
16726 modifier.selected === undefined
16727 ) {
16728 if (
16729 dt.rows(config.rows, $.extend({ selected: true }, modifier)).any()
16730 ) {
16731 $.extend(modifier, { selected: true });
16732 }
16733 }
16734
16735 var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray();
16736 var selectedCells = dt.cells(rowIndexes, config.columns, {
16737 order: modifier.order
16738 });
16739 var cells = selectedCells.render(config.orthogonal).toArray();
16740 var cellNodes = selectedCells.nodes().toArray();
16741 var cellIndexes = selectedCells.indexes().toArray();
16742
16743 var columns = dt.columns(config.columns).count();
16744 var rows = columns > 0 ? cells.length / columns : 0;
16745 var body = [];
16746 var cellCounter = 0;
16747
16748 for (var i = 0, ien = rows; i < ien; i++) {
16749 var row = [columns];
16750
16751 for (var j = 0; j < columns; j++) {
16752 row[j] = config.format.body(
16753 cells[cellCounter],
16754 cellIndexes[cellCounter].row,
16755 cellIndexes[cellCounter].column,
16756 cellNodes[cellCounter]
16757 );
16758 cellCounter++;
16759 }
16760
16761 body[i] = row;
16762 }
16763
16764 var data = {
16765 header: header,
16766 headerStructure: _headerFormatter(
16767 config.format.header,
16768 dt.table().header.structure(config.columns)
16769 ),
16770 footer: footer,
16771 footerStructure: _headerFormatter(
16772 config.format.footer,
16773 dt.table().footer.structure(config.columns)
16774 ),
16775 body: body
16776 };
16777
16778 if (config.customizeData) {
16779 config.customizeData(data);
16780 }
16781
16782 return data;
16783 };
16784
16785 function _headerFormatter(formatter, struct) {
16786 for (var i=0 ; i<struct.length ; i++) {
16787 for (var j=0 ; j<struct[i].length ; j++) {
16788 var item = struct[i][j];
16789
16790 if (item) {
16791 item.title = formatter(
16792 item.title,
16793 j,
16794 item.cell
16795 );
16796 }
16797 }
16798 }
16799
16800 return struct;
16801 }
16802
16803 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16804 * DataTables interface
16805 */
16806
16807 // Attach to DataTables objects for global access
16808 $.fn.dataTable.Buttons = Buttons;
16809 $.fn.DataTable.Buttons = Buttons;
16810
16811 // DataTables creation - check if the buttons have been defined for this table,
16812 // they will have been if the `B` option was used in `dom`, otherwise we should
16813 // create the buttons instance here so they can be inserted into the document
16814 // using the API. Listen for `init` for compatibility with pre 1.10.10, but to
16815 // be removed in future.
16816 $(document).on('init.dt plugin-init.dt', function (e, settings) {
16817 if (e.namespace !== 'dt') {
16818 return;
16819 }
16820
16821 var opts = settings.oInit.buttons || DataTable.defaults.buttons;
16822
16823 if (opts && !settings._buttons) {
16824 new Buttons(settings, opts).container();
16825 }
16826 });
16827
16828 function _init(settings, options) {
16829 var api = new DataTable.Api(settings);
16830 var opts = options
16831 ? options
16832 : api.init().buttons || DataTable.defaults.buttons;
16833
16834 return new Buttons(api, opts).container();
16835 }
16836
16837 // DataTables 1 `dom` feature option
16838 DataTable.ext.feature.push({
16839 fnInit: _init,
16840 cFeature: 'B'
16841 });
16842
16843 // DataTables 2 layout feature
16844 if (DataTable.feature) {
16845 DataTable.feature.register('buttons', _init);
16846 }
16847
16848
16849 return DataTable;
16850 }));
16851
16852
16853 /*! DataTables styling wrapper for Buttons
16854 * © SpryMedia Ltd - datatables.net/license
16855 */
16856
16857 (function( factory ){
16858 if ( typeof define === 'function' && define.amd ) {
16859 // AMD
16860 define( ['jquery', 'datatables.net-dt', 'datatables.net-buttons'], function ( $ ) {
16861 return factory( $, window, document );
16862 } );
16863 }
16864 else if ( typeof exports === 'object' ) {
16865 // CommonJS
16866 var jq = require('jquery');
16867 var cjsRequires = function (root, $) {
16868 if ( ! $.fn.dataTable ) {
16869 require('datatables.net-dt')(root, $);
16870 }
16871
16872 if ( ! $.fn.dataTable.Buttons ) {
16873 require('datatables.net-buttons')(root, $);
16874 }
16875 };
16876
16877 if (typeof window === 'undefined') {
16878 module.exports = function (root, $) {
16879 if ( ! root ) {
16880 // CommonJS environments without a window global must pass a
16881 // root. This will give an error otherwise
16882 root = window;
16883 }
16884
16885 if ( ! $ ) {
16886 $ = jq( root );
16887 }
16888
16889 cjsRequires( root, $ );
16890 return factory( $, root, root.document );
16891 };
16892 }
16893 else {
16894 cjsRequires( window, jq );
16895 module.exports = factory( jq, window, window.document );
16896 }
16897 }
16898 else {
16899 // Browser
16900 factory( jQuery, window, document );
16901 }
16902 }(function( $, window, document ) {
16903 'use strict';
16904 var DataTable = $.fn.dataTable;
16905
16906
16907
16908
16909 return DataTable;
16910 }));
16911
16912
16913 /*! Responsive 3.0.4
16914 * © SpryMedia Ltd - datatables.net/license
16915 */
16916
16917 (function( factory ){
16918 if ( typeof define === 'function' && define.amd ) {
16919 // AMD
16920 define( ['jquery', 'datatables.net'], function ( $ ) {
16921 return factory( $, window, document );
16922 } );
16923 }
16924 else if ( typeof exports === 'object' ) {
16925 // CommonJS
16926 var jq = require('jquery');
16927 var cjsRequires = function (root, $) {
16928 if ( ! $.fn.dataTable ) {
16929 require('datatables.net')(root, $);
16930 }
16931 };
16932
16933 if (typeof window === 'undefined') {
16934 module.exports = function (root, $) {
16935 if ( ! root ) {
16936 // CommonJS environments without a window global must pass a
16937 // root. This will give an error otherwise
16938 root = window;
16939 }
16940
16941 if ( ! $ ) {
16942 $ = jq( root );
16943 }
16944
16945 cjsRequires( root, $ );
16946 return factory( $, root, root.document );
16947 };
16948 }
16949 else {
16950 cjsRequires( window, jq );
16951 module.exports = factory( jq, window, window.document );
16952 }
16953 }
16954 else {
16955 // Browser
16956 factory( jQuery, window, document );
16957 }
16958 }(function( $, window, document ) {
16959 'use strict';
16960 var DataTable = $.fn.dataTable;
16961
16962
16963
16964 /**
16965 * @summary Responsive
16966 * @description Responsive tables plug-in for DataTables
16967 * @version 3.0.4
16968 * @author SpryMedia Ltd
16969 * @copyright SpryMedia Ltd.
16970 *
16971 * This source file is free software, available under the following license:
16972 * MIT license - http://datatables.net/license/mit
16973 *
16974 * This source file is distributed in the hope that it will be useful, but
16975 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16976 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
16977 *
16978 * For details please refer to: http://www.datatables.net
16979 */
16980
16981 /**
16982 * Responsive is a plug-in for the DataTables library that makes use of
16983 * DataTables' ability to change the visibility of columns, changing the
16984 * visibility of columns so the displayed columns fit into the table container.
16985 * The end result is that complex tables will be dynamically adjusted to fit
16986 * into the viewport, be it on a desktop, tablet or mobile browser.
16987 *
16988 * Responsive for DataTables has two modes of operation, which can used
16989 * individually or combined:
16990 *
16991 * * Class name based control - columns assigned class names that match the
16992 * breakpoint logic can be shown / hidden as required for each breakpoint.
16993 * * Automatic control - columns are automatically hidden when there is no
16994 * room left to display them. Columns removed from the right.
16995 *
16996 * In additional to column visibility control, Responsive also has built into
16997 * options to use DataTables' child row display to show / hide the information
16998 * from the table that has been hidden. There are also two modes of operation
16999 * for this child row display:
17000 *
17001 * * Inline - when the control element that the user can use to show / hide
17002 * child rows is displayed inside the first column of the table.
17003 * * Column - where a whole column is dedicated to be the show / hide control.
17004 *
17005 * Initialisation of Responsive is performed by:
17006 *
17007 * * Adding the class `responsive` or `dt-responsive` to the table. In this case
17008 * Responsive will automatically be initialised with the default configuration
17009 * options when the DataTable is created.
17010 * * Using the `responsive` option in the DataTables configuration options. This
17011 * can also be used to specify the configuration options, or simply set to
17012 * `true` to use the defaults.
17013 *
17014 * @class
17015 * @param {object} settings DataTables settings object for the host table
17016 * @param {object} [opts] Configuration options
17017 * @requires jQuery 1.7+
17018 * @requires DataTables 2.0.0+
17019 *
17020 * @example
17021 * $('#example').DataTable( {
17022 * responsive: true
17023 * } );
17024 * } );
17025 */
17026 var Responsive = function (settings, opts) {
17027 // Sanity check that we are using DataTables 2.0.0 or newer
17028 if (!DataTable.versionCheck || !DataTable.versionCheck('2')) {
17029 throw 'DataTables Responsive requires DataTables 2 or newer';
17030 }
17031
17032 this.s = {
17033 childNodeStore: {},
17034 columns: [],
17035 current: [],
17036 dt: new DataTable.Api(settings)
17037 };
17038
17039 // Check if responsive has already been initialised on this table
17040 if (this.s.dt.settings()[0].responsive) {
17041 return;
17042 }
17043
17044 // details is an object, but for simplicity the user can give it as a string
17045 // or a boolean
17046 if (opts && typeof opts.details === 'string') {
17047 opts.details = { type: opts.details };
17048 }
17049 else if (opts && opts.details === false) {
17050 opts.details = { type: false };
17051 }
17052 else if (opts && opts.details === true) {
17053 opts.details = { type: 'inline' };
17054 }
17055
17056 this.c = $.extend(
17057 true,
17058 {},
17059 Responsive.defaults,
17060 DataTable.defaults.responsive,
17061 opts
17062 );
17063 settings.responsive = this;
17064 this._constructor();
17065 };
17066
17067 $.extend(Responsive.prototype, {
17068 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
17069 * Constructor
17070 */
17071
17072 /**
17073 * Initialise the Responsive instance
17074 *
17075 * @private
17076 */
17077 _constructor: function () {
17078 var that = this;
17079 var dt = this.s.dt;
17080 var oldWindowWidth = $(window).innerWidth();
17081
17082 dt.settings()[0]._responsive = this;
17083
17084 // Use DataTables' throttle function to avoid processor thrashing
17085 $(window).on(
17086 'orientationchange.dtr',
17087 DataTable.util.throttle(function () {
17088 // iOS has a bug whereby resize can fire when only scrolling
17089 // See: http://stackoverflow.com/questions/8898412
17090 var width = $(window).innerWidth();
17091
17092 if (width !== oldWindowWidth) {
17093 that._resize();
17094 oldWindowWidth = width;
17095 }
17096 })
17097 );
17098
17099 // Handle new rows being dynamically added - needed as responsive
17100 // updates all rows (shown or not) a responsive change, rather than
17101 // per draw.
17102 dt.on('row-created.dtr', function (e, tr, data, idx) {
17103 if ($.inArray(false, that.s.current) !== -1) {
17104 $('>td, >th', tr).each(function (i) {
17105 var idx = dt.column.index('toData', i);
17106
17107 if (that.s.current[idx] === false) {
17108 $(this)
17109 .css('display', 'none')
17110 .addClass('dtr-hidden');
17111 }
17112 });
17113 }
17114 });
17115
17116 // Destroy event handler
17117 dt.on('destroy.dtr', function () {
17118 dt.off('.dtr');
17119 $(dt.table().body()).off('.dtr');
17120 $(window).off('resize.dtr orientationchange.dtr');
17121 dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control');
17122 $(dt.table().node()).removeClass('dtr-inline collapsed');
17123
17124 // Restore the columns that we've hidden
17125 $.each(that.s.current, function (i, val) {
17126 if (val === false) {
17127 that._setColumnVis(i, true);
17128 }
17129 });
17130 });
17131
17132 // Reorder the breakpoints array here in case they have been added out
17133 // of order
17134 this.c.breakpoints.sort(function (a, b) {
17135 return a.width < b.width ? 1 : a.width > b.width ? -1 : 0;
17136 });
17137
17138 this._classLogic();
17139
17140 // Details handler
17141 var details = this.c.details;
17142
17143 if (details.type !== false) {
17144 that._detailsInit();
17145
17146 // DataTables will trigger this event on every column it shows and
17147 // hides individually
17148 dt.on('column-visibility.dtr', function () {
17149 // Use a small debounce to allow multiple columns to be set together
17150 if (that._timer) {
17151 clearTimeout(that._timer);
17152 }
17153
17154 that._timer = setTimeout(function () {
17155 that._timer = null;
17156
17157 that._classLogic();
17158 that._resizeAuto();
17159 that._resize(true);
17160
17161 that._redrawChildren();
17162 }, 100);
17163 });
17164
17165 // Redraw the details box on each draw which will happen if the data
17166 // has changed. This is used until DataTables implements a native
17167 // `updated` event for rows
17168 dt.on('draw.dtr', function () {
17169 that._redrawChildren();
17170 });
17171
17172 $(dt.table().node()).addClass('dtr-' + details.type);
17173 }
17174
17175 // DT2 let's us tell it if we are hiding columns
17176 dt.on('column-calc.dt', function (e, d) {
17177 var curr = that.s.current;
17178
17179 for (var i = 0; i < curr.length; i++) {
17180 var idx = d.visible.indexOf(i);
17181
17182 if (curr[i] === false && idx >= 0) {
17183 d.visible.splice(idx, 1);
17184 }
17185 }
17186 });
17187
17188 // On Ajax reload we want to reopen any child rows which are displayed
17189 // by responsive
17190 dt.on('preXhr.dtr', function () {
17191 var rowIds = [];
17192 dt.rows().every(function () {
17193 if (this.child.isShown()) {
17194 rowIds.push(this.id(true));
17195 }
17196 });
17197
17198 dt.one('draw.dtr', function () {
17199 that._resizeAuto();
17200 that._resize();
17201
17202 dt.rows(rowIds).every(function () {
17203 that._detailsDisplay(this, false);
17204 });
17205 });
17206 });
17207
17208 // First pass when the table is ready
17209 dt
17210 .on('draw.dtr', function () {
17211 that._controlClass();
17212 })
17213 .ready(function () {
17214 that._resizeAuto();
17215 that._resize();
17216
17217 // Attach listeners after first pass
17218 dt.on('column-reorder.dtr', function (e, settings, details) {
17219 that._classLogic();
17220 that._resizeAuto();
17221 that._resize(true);
17222 });
17223
17224 // Change in column sizes means we need to calc
17225 dt.on('column-sizing.dtr', function () {
17226 that._resizeAuto();
17227 that._resize();
17228 });
17229 });
17230 },
17231
17232 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
17233 * Private methods
17234 */
17235
17236 /**
17237 * Insert a `col` tag into the correct location in a `colgroup`.
17238 *
17239 * @param {jQuery} colGroup The `colgroup` tag
17240 * @param {jQuery} colEl The `col` tag
17241 */
17242 _colGroupAttach: function (colGroup, colEls, idx) {
17243 var found = null;
17244
17245 // No need to do anything if already attached
17246 if (colEls[idx].get(0).parentNode === colGroup[0]) {
17247 return;
17248 }
17249
17250 // Find the first `col` after our own which is already attached
17251 for (var i = idx+1; i < colEls.length; i++) {
17252 if (colGroup[0] === colEls[i].get(0).parentNode) {
17253 found = i;
17254 break;
17255 }
17256 }
17257
17258 if (found !== null) {
17259 // Insert before
17260 colEls[idx].insertBefore(colEls[found][0]);
17261 }
17262 else {
17263 // If wasn't found, insert at the end
17264 colGroup.append(colEls[idx]);
17265 }
17266 },
17267
17268 /**
17269 * Get and store nodes from a cell - use for node moving renderers
17270 *
17271 * @param {*} dt DT instance
17272 * @param {*} row Row index
17273 * @param {*} col Column index
17274 */
17275 _childNodes: function (dt, row, col) {
17276 var name = row + '-' + col;
17277
17278 if (this.s.childNodeStore[name]) {
17279 return this.s.childNodeStore[name];
17280 }
17281
17282 // https://jsperf.com/childnodes-array-slice-vs-loop
17283 var nodes = [];
17284 var children = dt.cell(row, col).node().childNodes;
17285 for (var i = 0, ien = children.length; i < ien; i++) {
17286 nodes.push(children[i]);
17287 }
17288
17289 this.s.childNodeStore[name] = nodes;
17290
17291 return nodes;
17292 },
17293
17294 /**
17295 * Restore nodes from the cache to a table cell
17296 *
17297 * @param {*} dt DT instance
17298 * @param {*} row Row index
17299 * @param {*} col Column index
17300 */
17301 _childNodesRestore: function (dt, row, col) {
17302 var name = row + '-' + col;
17303
17304 if (!this.s.childNodeStore[name]) {
17305 return;
17306 }
17307
17308 var node = dt.cell(row, col).node();
17309 var store = this.s.childNodeStore[name];
17310 if (store.length > 0) {
17311 var parent = store[0].parentNode;
17312 var parentChildren = parent.childNodes;
17313 var a = [];
17314
17315 for (var i = 0, ien = parentChildren.length; i < ien; i++) {
17316 a.push(parentChildren[i]);
17317 }
17318
17319 for (var j = 0, jen = a.length; j < jen; j++) {
17320 node.appendChild(a[j]);
17321 }
17322 }
17323
17324 this.s.childNodeStore[name] = undefined;
17325 },
17326
17327 /**
17328 * Calculate the visibility for the columns in a table for a given
17329 * breakpoint. The result is pre-determined based on the class logic if
17330 * class names are used to control all columns, but the width of the table
17331 * is also used if there are columns which are to be automatically shown
17332 * and hidden.
17333 *
17334 * @param {string} breakpoint Breakpoint name to use for the calculation
17335 * @return {array} Array of boolean values initiating the visibility of each
17336 * column.
17337 * @private
17338 */
17339 _columnsVisiblity: function (breakpoint) {
17340 var dt = this.s.dt;
17341 var columns = this.s.columns;
17342 var i, ien;
17343
17344 // Create an array that defines the column ordering based first on the
17345 // column's priority, and secondly the column index. This allows the
17346 // columns to be removed from the right if the priority matches
17347 var order = columns
17348 .map(function (col, idx) {
17349 return {
17350 columnIdx: idx,
17351 priority: col.priority
17352 };
17353 })
17354 .sort(function (a, b) {
17355 if (a.priority !== b.priority) {
17356 return a.priority - b.priority;
17357 }
17358 return a.columnIdx - b.columnIdx;
17359 });
17360
17361 // Class logic - determine which columns are in this breakpoint based
17362 // on the classes. If no class control (i.e. `auto`) then `-` is used
17363 // to indicate this to the rest of the function
17364 var display = $.map(columns, function (col, i) {
17365 if (dt.column(i).visible() === false) {
17366 return 'not-visible';
17367 }
17368 return col.auto && col.minWidth === null
17369 ? false
17370 : col.auto === true
17371 ? '-'
17372 : $.inArray(breakpoint, col.includeIn) !== -1;
17373 });
17374
17375 // Auto column control - first pass: how much width is taken by the
17376 // ones that must be included from the non-auto columns
17377 var requiredWidth = 0;
17378 for (i = 0, ien = display.length; i < ien; i++) {
17379 if (display[i] === true) {
17380 requiredWidth += columns[i].minWidth;
17381 }
17382 }
17383
17384 // Second pass, use up any remaining width for other columns. For
17385 // scrolling tables we need to subtract the width of the scrollbar. It
17386 // may not be requires which makes this sub-optimal, but it would
17387 // require another full redraw to make complete use of those extra few
17388 // pixels
17389 var scrolling = dt.settings()[0].oScroll;
17390 var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
17391 var widthAvailable = dt.table().container().offsetWidth - bar;
17392 var usedWidth = widthAvailable - requiredWidth;
17393
17394 // Control column needs to always be included. This makes it sub-
17395 // optimal in terms of using the available with, but to stop layout
17396 // thrashing or overflow. Also we need to account for the control column
17397 // width first so we know how much width is available for the other
17398 // columns, since the control column might not be the first one shown
17399 for (i = 0, ien = display.length; i < ien; i++) {
17400 if (columns[i].control) {
17401 usedWidth -= columns[i].minWidth;
17402 }
17403 }
17404
17405 // Allow columns to be shown (counting by priority and then right to
17406 // left) until we run out of room
17407 var empty = false;
17408 for (i = 0, ien = order.length; i < ien; i++) {
17409 var colIdx = order[i].columnIdx;
17410
17411 if (
17412 display[colIdx] === '-' &&
17413 !columns[colIdx].control &&
17414 columns[colIdx].minWidth
17415 ) {
17416 // Once we've found a column that won't fit we don't let any
17417 // others display either, or columns might disappear in the
17418 // middle of the table
17419 if (empty || usedWidth - columns[colIdx].minWidth < 0) {
17420 empty = true;
17421 display[colIdx] = false;
17422 }
17423 else {
17424 display[colIdx] = true;
17425 }
17426
17427 usedWidth -= columns[colIdx].minWidth;
17428 }
17429 }
17430
17431 // Determine if the 'control' column should be shown (if there is one).
17432 // This is the case when there is a hidden column (that is not the
17433 // control column). The two loops look inefficient here, but they are
17434 // trivial and will fly through. We need to know the outcome from the
17435 // first , before the action in the second can be taken
17436 var showControl = false;
17437
17438 for (i = 0, ien = columns.length; i < ien; i++) {
17439 if (
17440 !columns[i].control &&
17441 !columns[i].never &&
17442 display[i] === false
17443 ) {
17444 showControl = true;
17445 break;
17446 }
17447 }
17448
17449 for (i = 0, ien = columns.length; i < ien; i++) {
17450 if (columns[i].control) {
17451 display[i] = showControl;
17452 }
17453
17454 // Replace not visible string with false from the control column detection above
17455 if (display[i] === 'not-visible') {
17456 display[i] = false;
17457 }
17458 }
17459
17460 // Finally we need to make sure that there is at least one column that
17461 // is visible
17462 if ($.inArray(true, display) === -1) {
17463 display[0] = true;
17464 }
17465
17466 return display;
17467 },
17468
17469 /**
17470 * Create the internal `columns` array with information about the columns
17471 * for the table. This includes determining which breakpoints the column
17472 * will appear in, based upon class names in the column, which makes up the
17473 * vast majority of this method.
17474 *
17475 * @private
17476 */
17477 _classLogic: function () {
17478 var that = this;
17479 var breakpoints = this.c.breakpoints;
17480 var dt = this.s.dt;
17481 var columns = dt
17482 .columns()
17483 .eq(0)
17484 .map(function (i) {
17485 var column = this.column(i);
17486 var className = column.header().className;
17487 var priority = column.init().responsivePriority;
17488 var dataPriority = column
17489 .header()
17490 .getAttribute('data-priority');
17491
17492 if (priority === undefined) {
17493 priority =
17494 dataPriority === undefined || dataPriority === null
17495 ? 10000
17496 : dataPriority * 1;
17497 }
17498
17499 return {
17500 className: className,
17501 includeIn: [],
17502 auto: false,
17503 control: false,
17504 never: className.match(/\b(dtr\-)?never\b/) ? true : false,
17505 priority: priority
17506 };
17507 });
17508
17509 // Simply add a breakpoint to `includeIn` array, ensuring that there are
17510 // no duplicates
17511 var add = function (colIdx, name) {
17512 var includeIn = columns[colIdx].includeIn;
17513
17514 if ($.inArray(name, includeIn) === -1) {
17515 includeIn.push(name);
17516 }
17517 };
17518
17519 var column = function (colIdx, name, operator, matched) {
17520 var size, i, ien;
17521
17522 if (!operator) {
17523 columns[colIdx].includeIn.push(name);
17524 }
17525 else if (operator === 'max-') {
17526 // Add this breakpoint and all smaller
17527 size = that._find(name).width;
17528
17529 for (i = 0, ien = breakpoints.length; i < ien; i++) {
17530 if (breakpoints[i].width <= size) {
17531 add(colIdx, breakpoints[i].name);
17532 }
17533 }
17534 }
17535 else if (operator === 'min-') {
17536 // Add this breakpoint and all larger
17537 size = that._find(name).width;
17538
17539 for (i = 0, ien = breakpoints.length; i < ien; i++) {
17540 if (breakpoints[i].width >= size) {
17541 add(colIdx, breakpoints[i].name);
17542 }
17543 }
17544 }
17545 else if (operator === 'not-') {
17546 // Add all but this breakpoint
17547 for (i = 0, ien = breakpoints.length; i < ien; i++) {
17548 if (breakpoints[i].name.indexOf(matched) === -1) {
17549 add(colIdx, breakpoints[i].name);
17550 }
17551 }
17552 }
17553 };
17554
17555 // Loop over each column and determine if it has a responsive control
17556 // class
17557 columns.each(function (col, i) {
17558 var classNames = col.className.split(' ');
17559 var hasClass = false;
17560
17561 // Split the class name up so multiple rules can be applied if needed
17562 for (var k = 0, ken = classNames.length; k < ken; k++) {
17563 var className = classNames[k].trim();
17564
17565 if (className === 'all' || className === 'dtr-all') {
17566 // Include in all
17567 hasClass = true;
17568 col.includeIn = $.map(breakpoints, function (a) {
17569 return a.name;
17570 });
17571 return;
17572 }
17573 else if (
17574 className === 'none' ||
17575 className === 'dtr-none' ||
17576 col.never
17577 ) {
17578 // Include in none (default) and no auto
17579 hasClass = true;
17580 return;
17581 }
17582 else if (
17583 className === 'control' ||
17584 className === 'dtr-control'
17585 ) {
17586 // Special column that is only visible, when one of the other
17587 // columns is hidden. This is used for the details control
17588 hasClass = true;
17589 col.control = true;
17590 return;
17591 }
17592
17593 $.each(breakpoints, function (j, breakpoint) {
17594 // Does this column have a class that matches this breakpoint?
17595 var brokenPoint = breakpoint.name.split('-');
17596 var re = new RegExp(
17597 '(min\\-|max\\-|not\\-)?(' +
17598 brokenPoint[0] +
17599 ')(\\-[_a-zA-Z0-9])?'
17600 );
17601 var match = className.match(re);
17602
17603 if (match) {
17604 hasClass = true;
17605
17606 if (
17607 match[2] === brokenPoint[0] &&
17608 match[3] === '-' + brokenPoint[1]
17609 ) {
17610 // Class name matches breakpoint name fully
17611 column(
17612 i,
17613 breakpoint.name,
17614 match[1],
17615 match[2] + match[3]
17616 );
17617 }
17618 else if (match[2] === brokenPoint[0] && !match[3]) {
17619 // Class name matched primary breakpoint name with no qualifier
17620 column(i, breakpoint.name, match[1], match[2]);
17621 }
17622 }
17623 });
17624 }
17625
17626 // If there was no control class, then automatic sizing is used
17627 if (!hasClass) {
17628 col.auto = true;
17629 }
17630 });
17631
17632 this.s.columns = columns;
17633 },
17634
17635 /**
17636 * Update the cells to show the correct control class / button
17637 * @private
17638 */
17639 _controlClass: function () {
17640 if (this.c.details.type === 'inline') {
17641 var dt = this.s.dt;
17642 var columnsVis = this.s.current;
17643 var firstVisible = $.inArray(true, columnsVis);
17644
17645 // Remove from any cells which shouldn't have it
17646 dt.cells(
17647 null,
17648 function (idx) {
17649 return idx !== firstVisible;
17650 },
17651 { page: 'current' }
17652 )
17653 .nodes()
17654 .to$()
17655 .filter('.dtr-control')
17656 .removeClass('dtr-control');
17657
17658 if (firstVisible >= 0) {
17659 dt.cells(null, firstVisible, { page: 'current' })
17660 .nodes()
17661 .to$()
17662 .addClass('dtr-control');
17663 }
17664 }
17665
17666 this._tabIndexes();
17667 },
17668
17669 /**
17670 * Show the details for the child row
17671 *
17672 * @param {DataTables.Api} row API instance for the row
17673 * @param {boolean} update Update flag
17674 * @private
17675 */
17676 _detailsDisplay: function (row, update) {
17677 var that = this;
17678 var dt = this.s.dt;
17679 var details = this.c.details;
17680 var event = function (res) {
17681 $(row.node()).toggleClass('dtr-expanded', res !== false);
17682 $(dt.table().node()).triggerHandler('responsive-display.dt', [
17683 dt,
17684 row,
17685 res,
17686 update
17687 ]);
17688 };
17689
17690 if (details && details.type !== false) {
17691 var renderer =
17692 typeof details.renderer === 'string'
17693 ? Responsive.renderer[details.renderer]()
17694 : details.renderer;
17695
17696 var res = details.display(
17697 row,
17698 update,
17699 function () {
17700 return renderer.call(
17701 that,
17702 dt,
17703 row[0][0],
17704 that._detailsObj(row[0])
17705 );
17706 },
17707 function () {
17708 event(false);
17709 }
17710 );
17711
17712 if (typeof res === 'boolean') {
17713 event(res);
17714 }
17715 }
17716 },
17717
17718 /**
17719 * Initialisation for the details handler
17720 *
17721 * @private
17722 */
17723 _detailsInit: function () {
17724 var that = this;
17725 var dt = this.s.dt;
17726 var details = this.c.details;
17727
17728 // The inline type always uses the first child as the target
17729 if (details.type === 'inline') {
17730 details.target = 'td.dtr-control, th.dtr-control';
17731 }
17732
17733 $(dt.table().body()).on('keyup.dtr', 'td, th', function (e) {
17734 if (e.keyCode === 13 && $(this).data('dtr-keyboard')) {
17735 $(this).click();
17736 }
17737 });
17738
17739 // type.target can be a string jQuery selector or a column index
17740 var target = details.target;
17741 var selector = typeof target === 'string' ? target : 'td, th';
17742
17743 if (target !== undefined || target !== null) {
17744 // Click handler to show / hide the details rows when they are available
17745 $(dt.table().body()).on(
17746 'click.dtr mousedown.dtr mouseup.dtr',
17747 selector,
17748 function (e) {
17749 // If the table is not collapsed (i.e. there is no hidden columns)
17750 // then take no action
17751 if (!$(dt.table().node()).hasClass('collapsed')) {
17752 return;
17753 }
17754
17755 // Check that the row is actually a DataTable's controlled node
17756 if (
17757 $.inArray(
17758 $(this).closest('tr').get(0),
17759 dt.rows().nodes().toArray()
17760 ) === -1
17761 ) {
17762 return;
17763 }
17764
17765 // For column index, we determine if we should act or not in the
17766 // handler - otherwise it is already okay
17767 if (typeof target === 'number') {
17768 var targetIdx =
17769 target < 0
17770 ? dt.columns().eq(0).length + target
17771 : target;
17772
17773 if (dt.cell(this).index().column !== targetIdx) {
17774 return;
17775 }
17776 }
17777
17778 // $().closest() includes itself in its check
17779 var row = dt.row($(this).closest('tr'));
17780
17781 // Check event type to do an action
17782 if (e.type === 'click') {
17783 // The renderer is given as a function so the caller can execute it
17784 // only when they need (i.e. if hiding there is no point is running
17785 // the renderer)
17786 that._detailsDisplay(row, false);
17787 }
17788 else if (e.type === 'mousedown') {
17789 // For mouse users, prevent the focus ring from showing
17790 $(this).css('outline', 'none');
17791 }
17792 else if (e.type === 'mouseup') {
17793 // And then re-allow at the end of the click
17794 $(this).trigger('blur').css('outline', '');
17795 }
17796 }
17797 );
17798 }
17799 },
17800
17801 /**
17802 * Get the details to pass to a renderer for a row
17803 * @param {int} rowIdx Row index
17804 * @private
17805 */
17806 _detailsObj: function (rowIdx) {
17807 var that = this;
17808 var dt = this.s.dt;
17809
17810 return $.map(this.s.columns, function (col, i) {
17811 // Never and control columns should not be passed to the renderer
17812 if (col.never || col.control) {
17813 return;
17814 }
17815
17816 var dtCol = dt.settings()[0].aoColumns[i];
17817
17818 return {
17819 className: dtCol.sClass,
17820 columnIndex: i,
17821 data: dt.cell(rowIdx, i).render(that.c.orthogonal),
17822 hidden: dt.column(i).visible() && !that.s.current[i],
17823 rowIndex: rowIdx,
17824 title: dt.column(i).title()
17825 };
17826 });
17827 },
17828
17829 /**
17830 * Find a breakpoint object from a name
17831 *
17832 * @param {string} name Breakpoint name to find
17833 * @return {object} Breakpoint description object
17834 * @private
17835 */
17836 _find: function (name) {
17837 var breakpoints = this.c.breakpoints;
17838
17839 for (var i = 0, ien = breakpoints.length; i < ien; i++) {
17840 if (breakpoints[i].name === name) {
17841 return breakpoints[i];
17842 }
17843 }
17844 },
17845
17846 /**
17847 * Re-create the contents of the child rows as the display has changed in
17848 * some way.
17849 *
17850 * @private
17851 */
17852 _redrawChildren: function () {
17853 var that = this;
17854 var dt = this.s.dt;
17855
17856 dt.rows({ page: 'current' }).iterator('row', function (settings, idx) {
17857 that._detailsDisplay(dt.row(idx), true);
17858 });
17859 },
17860
17861 /**
17862 * Alter the table display for a resized viewport. This involves first
17863 * determining what breakpoint the window currently is in, getting the
17864 * column visibilities to apply and then setting them.
17865 *
17866 * @param {boolean} forceRedraw Force a redraw
17867 * @private
17868 */
17869 _resize: function (forceRedraw) {
17870 var that = this;
17871 var dt = this.s.dt;
17872 var width = $(window).innerWidth();
17873 var breakpoints = this.c.breakpoints;
17874 var breakpoint = breakpoints[0].name;
17875 var columns = this.s.columns;
17876 var i, ien;
17877 var oldVis = this.s.current.slice();
17878
17879 // Determine what breakpoint we are currently at
17880 for (i = breakpoints.length - 1; i >= 0; i--) {
17881 if (width <= breakpoints[i].width) {
17882 breakpoint = breakpoints[i].name;
17883 break;
17884 }
17885 }
17886
17887 // Show the columns for that break point
17888 var columnsVis = this._columnsVisiblity(breakpoint);
17889 this.s.current = columnsVis;
17890
17891 // Set the class before the column visibility is changed so event
17892 // listeners know what the state is. Need to determine if there are
17893 // any columns that are not visible but can be shown
17894 var collapsedClass = false;
17895
17896 for (i = 0, ien = columns.length; i < ien; i++) {
17897 if (
17898 columnsVis[i] === false &&
17899 !columns[i].never &&
17900 !columns[i].control &&
17901 !dt.column(i).visible() === false
17902 ) {
17903 collapsedClass = true;
17904 break;
17905 }
17906 }
17907
17908 $(dt.table().node()).toggleClass('collapsed', collapsedClass);
17909
17910 var changed = false;
17911 var visible = 0;
17912 var dtSettings = dt.settings()[0];
17913 var colGroup = $(dt.table().node()).children('colgroup');
17914 var colEls = dtSettings.aoColumns.map(function (col) {
17915 return col.colEl;
17916 });
17917
17918 dt.columns()
17919 .eq(0)
17920 .each(function (colIdx, i) {
17921 // Do nothing on DataTables' hidden column - DT removes it from the table
17922 // so we need to slide back
17923 if (! dt.column(colIdx).visible()) {
17924 return;
17925 }
17926
17927 if (columnsVis[i] === true) {
17928 visible++;
17929 }
17930
17931 if (forceRedraw || columnsVis[i] !== oldVis[i]) {
17932 changed = true;
17933 that._setColumnVis(colIdx, columnsVis[i]);
17934 }
17935
17936 // DataTables 2 uses `col` to define the width for a column
17937 // and this needs to run each time, as DataTables will change
17938 // the column width. We may need to reattach if we've removed
17939 // an element previously.
17940 if (! columnsVis[i]) {
17941 colEls[i].detach();
17942 }
17943 else {
17944 that._colGroupAttach(colGroup, colEls, i);
17945 }
17946 });
17947
17948 if (changed) {
17949 dt.columns.adjust();
17950
17951 this._redrawChildren();
17952
17953 // Inform listeners of the change
17954 $(dt.table().node()).trigger('responsive-resize.dt', [
17955 dt,
17956 this._responsiveOnlyHidden()
17957 ]);
17958
17959 // If no records, update the "No records" display element
17960 if (dt.page.info().recordsDisplay === 0) {
17961 $('td', dt.table().body()).eq(0).attr('colspan', visible);
17962 }
17963 }
17964
17965 that._controlClass();
17966 },
17967
17968 /**
17969 * Determine the width of each column in the table so the auto column hiding
17970 * has that information to work with. This method is never going to be 100%
17971 * perfect since column widths can change slightly per page, but without
17972 * seriously compromising performance this is quite effective.
17973 *
17974 * @private
17975 */
17976 _resizeAuto: function () {
17977 var dt = this.s.dt;
17978 var columns = this.s.columns;
17979 var that = this;
17980 var visibleColumns = dt
17981 .columns()
17982 .indexes()
17983 .filter(function (idx) {
17984 return dt.column(idx).visible();
17985 });
17986
17987 // Are we allowed to do auto sizing?
17988 if (!this.c.auto) {
17989 return;
17990 }
17991
17992 // Are there any columns that actually need auto-sizing, or do they all
17993 // have classes defined
17994 if (
17995 $.inArray(
17996 true,
17997 $.map(columns, function (c) {
17998 return c.auto;
17999 })
18000 ) === -1
18001 ) {
18002 return;
18003 }
18004
18005 // Clone the table with the current data in it
18006 var clonedTable = dt.table().node().cloneNode(false);
18007 var clonedHeader = $(dt.table().header().cloneNode(false)).appendTo(
18008 clonedTable
18009 );
18010 var clonedFooter = $(dt.table().footer().cloneNode(false)).appendTo(
18011 clonedTable
18012 );
18013 var clonedBody = $(dt.table().body())
18014 .clone(false, false)
18015 .empty()
18016 .appendTo(clonedTable); // use jQuery because of IE8
18017
18018 clonedTable.style.width = 'auto';
18019
18020 // Header
18021 dt.table()
18022 .header.structure(visibleColumns)
18023 .forEach((row) => {
18024 var cells = row
18025 .filter(function (el) {
18026 return el ? true : false;
18027 })
18028 .map(function (el) {
18029 return $(el.cell)
18030 .clone(false)
18031 .css('display', 'table-cell')
18032 .css('width', 'auto')
18033 .css('min-width', 0);
18034 });
18035
18036 $('<tr/>').append(cells).appendTo(clonedHeader);
18037 });
18038
18039 // Always need an empty row that we can read widths from
18040 var emptyRow = $('<tr/>').appendTo(clonedBody);
18041
18042 for (var i = 0; i < visibleColumns.count(); i++) {
18043 emptyRow.append('<td/>');
18044 }
18045
18046 // Body rows
18047 if (this.c.details.renderer._responsiveMovesNodes) {
18048 // Slow but it allows for moving elements around the document
18049 dt.rows({ page: 'current' }).every(function (rowIdx) {
18050 var node = this.node();
18051
18052 if (! node) {
18053 return;
18054 }
18055
18056 // We clone the table's rows and cells to create the sizing table
18057 var tr = node.cloneNode(false);
18058
18059 dt.cells(rowIdx, visibleColumns).every(function (rowIdx2, colIdx) {
18060 // If nodes have been moved out (listHiddenNodes), we need to
18061 // clone from the store
18062 var store = that.s.childNodeStore[rowIdx + '-' + colIdx];
18063
18064 if (store) {
18065 $(this.node().cloneNode(false))
18066 .append($(store).clone())
18067 .appendTo(tr);
18068 }
18069 else {
18070 $(this.node()).clone(false).appendTo(tr);
18071 }
18072 });
18073
18074 clonedBody.append(tr);
18075 });
18076 }
18077 else {
18078 // This is much faster, but it doesn't account for moving nodes around
18079 $(clonedBody)
18080 .append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
18081 .find( 'th, td' ).css( 'display', '' );
18082 }
18083
18084 // Any cells which were hidden by Responsive in the host table, need to
18085 // be visible here for the calculations
18086 clonedBody.find('th, td').css('display', '');
18087
18088 // Footer
18089 dt.table()
18090 .footer.structure(visibleColumns)
18091 .forEach((row) => {
18092 var cells = row
18093 .filter(function (el) {
18094 return el ? true : false;
18095 })
18096 .map(function (el) {
18097 return $(el.cell)
18098 .clone(false)
18099 .css('display', 'table-cell')
18100 .css('width', 'auto')
18101 .css('min-width', 0);
18102 });
18103
18104 $('<tr/>').append(cells).appendTo(clonedFooter);
18105 });
18106
18107 // In the inline case extra padding is applied to the first column to
18108 // give space for the show / hide icon. We need to use this in the
18109 // calculation
18110 if (this.c.details.type === 'inline') {
18111 $(clonedTable).addClass('dtr-inline collapsed');
18112 }
18113
18114 // It is unsafe to insert elements with the same name into the DOM
18115 // multiple times. For example, cloning and inserting a checked radio
18116 // clears the chcecked state of the original radio.
18117 $(clonedTable).find('[name]').removeAttr('name');
18118
18119 // A position absolute table would take the table out of the flow of
18120 // our container element, bypassing the height and width (Scroller)
18121 $(clonedTable).css('position', 'relative');
18122
18123 var inserted = $('<div/>')
18124 .css({
18125 width: 1,
18126 height: 1,
18127 overflow: 'hidden',
18128 clear: 'both'
18129 })
18130 .append(clonedTable);
18131
18132 inserted.insertBefore(dt.table().node());
18133
18134 // The cloned table now contains the smallest that each column can be
18135 emptyRow.children().each(function (i) {
18136 var idx = dt.column.index('fromVisible', i);
18137 columns[idx].minWidth = this.offsetWidth || 0;
18138 });
18139
18140 inserted.remove();
18141 },
18142
18143 /**
18144 * Get the state of the current hidden columns - controlled by Responsive only
18145 */
18146 _responsiveOnlyHidden: function () {
18147 var dt = this.s.dt;
18148
18149 return $.map(this.s.current, function (v, i) {
18150 // If the column is hidden by DataTables then it can't be hidden by
18151 // Responsive!
18152 if (dt.column(i).visible() === false) {
18153 return true;
18154 }
18155 return v;
18156 });
18157 },
18158
18159 /**
18160 * Set a column's visibility.
18161 *
18162 * We don't use DataTables' column visibility controls in order to ensure
18163 * that column visibility can Responsive can no-exist. Since only IE8+ is
18164 * supported (and all evergreen browsers of course) the control of the
18165 * display attribute works well.
18166 *
18167 * @param {integer} col Column index
18168 * @param {boolean} showHide Show or hide (true or false)
18169 * @private
18170 */
18171 _setColumnVis: function (col, showHide) {
18172 var that = this;
18173 var dt = this.s.dt;
18174 var display = showHide ? '' : 'none'; // empty string will remove the attr
18175
18176 this._setHeaderVis(col, showHide, dt.table().header.structure());
18177 this._setHeaderVis(col, showHide, dt.table().footer.structure());
18178
18179 dt.column(col)
18180 .nodes()
18181 .to$()
18182 .css('display', display)
18183 .toggleClass('dtr-hidden', !showHide);
18184
18185 // If the are child nodes stored, we might need to reinsert them
18186 if (!$.isEmptyObject(this.s.childNodeStore)) {
18187 dt.cells(null, col)
18188 .indexes()
18189 .each(function (idx) {
18190 that._childNodesRestore(dt, idx.row, idx.column);
18191 });
18192 }
18193 },
18194
18195 /**
18196 * Set the a column's visibility, taking into account multiple rows
18197 * in a header / footer and colspan attributes
18198 * @param {*} col
18199 * @param {*} showHide
18200 * @param {*} structure
18201 */
18202 _setHeaderVis: function (col, showHide, structure) {
18203 var that = this;
18204 var display = showHide ? '' : 'none';
18205
18206 // We use the `null`s in the structure array to indicate that a cell
18207 // should expand over that one if there is a colspan, but it might
18208 // also have been filled by a rowspan, so we need to expand the
18209 // rowspan cells down through the structure
18210 structure.forEach(function (row, rowIdx) {
18211 for (var col = 0; col < row.length; col++) {
18212 if (row[col] && row[col].rowspan > 1) {
18213 var span = row[col].rowspan;
18214
18215 for (var i=1 ; i<span ; i++) {
18216 structure[rowIdx + i][col] = {};
18217 }
18218 }
18219 }
18220 });
18221
18222 structure.forEach(function (row) {
18223 if (row[col] && row[col].cell) {
18224 $(row[col].cell)
18225 .css('display', display)
18226 .toggleClass('dtr-hidden', !showHide);
18227 }
18228 else {
18229 // In a colspan - need to rewind calc the new span since
18230 // display:none elements do not count as being spanned over
18231 var search = col;
18232
18233 while (search >= 0) {
18234 if (row[search] && row[search].cell) {
18235 row[search].cell.colSpan = that._colspan(row, search);
18236 break;
18237 }
18238
18239 search--;
18240 }
18241 }
18242 });
18243 },
18244
18245 /**
18246 * How many columns should this cell span
18247 *
18248 * @param {*} row Header structure row
18249 * @param {*} idx The column index of the cell to span
18250 */
18251 _colspan: function (row, idx) {
18252 var colspan = 1;
18253
18254 for (var col = idx + 1; col < row.length; col++) {
18255 if (row[col] === null && this.s.current[col]) {
18256 // colspan and not hidden by Responsive
18257 colspan++;
18258 }
18259 else if (row[col]) {
18260 // Got the next cell, jump out
18261 break;
18262 }
18263 }
18264
18265 return colspan;
18266 },
18267
18268 /**
18269 * Update the cell tab indexes for keyboard accessibility. This is called on
18270 * every table draw - that is potentially inefficient, but also the least
18271 * complex option given that column visibility can change on the fly. Its a
18272 * shame user-focus was removed from CSS 3 UI, as it would have solved this
18273 * issue with a single CSS statement.
18274 *
18275 * @private
18276 */
18277 _tabIndexes: function () {
18278 var dt = this.s.dt;
18279 var cells = dt.cells({ page: 'current' }).nodes().to$();
18280 var ctx = dt.settings()[0];
18281 var target = this.c.details.target;
18282
18283 cells.filter('[data-dtr-keyboard]').removeData('[data-dtr-keyboard]');
18284
18285 if (typeof target === 'number') {
18286 dt.cells(null, target, { page: 'current' })
18287 .nodes()
18288 .to$()
18289 .attr('tabIndex', ctx.iTabIndex)
18290 .data('dtr-keyboard', 1);
18291 }
18292 else {
18293 // This is a bit of a hack - we need to limit the selected nodes to just
18294 // those of this table
18295 if (target === 'td:first-child, th:first-child') {
18296 target = '>td:first-child, >th:first-child';
18297 }
18298
18299 var rows = dt.rows({ page: 'current' }).nodes();
18300 var nodes = target === 'tr'
18301 ? $(rows)
18302 : $(target, rows);
18303
18304 nodes
18305 .attr('tabIndex', ctx.iTabIndex)
18306 .data('dtr-keyboard', 1);
18307 }
18308 }
18309 });
18310
18311 /**
18312 * List of default breakpoints. Each item in the array is an object with two
18313 * properties:
18314 *
18315 * * `name` - the breakpoint name.
18316 * * `width` - the breakpoint width
18317 *
18318 * @name Responsive.breakpoints
18319 * @static
18320 */
18321 Responsive.breakpoints = [
18322 { name: 'desktop', width: Infinity },
18323 { name: 'tablet-l', width: 1024 },
18324 { name: 'tablet-p', width: 768 },
18325 { name: 'mobile-l', width: 480 },
18326 { name: 'mobile-p', width: 320 }
18327 ];
18328
18329 /**
18330 * Display methods - functions which define how the hidden data should be shown
18331 * in the table.
18332 *
18333 * @namespace
18334 * @name Responsive.defaults
18335 * @static
18336 */
18337 Responsive.display = {
18338 childRow: function (row, update, render) {
18339 var rowNode = $(row.node());
18340
18341 if (update) {
18342 if (rowNode.hasClass('dtr-expanded')) {
18343 row.child(render(), 'child').show();
18344
18345 return true;
18346 }
18347 }
18348 else {
18349 if (!rowNode.hasClass('dtr-expanded')) {
18350 var rendered = render();
18351
18352 if (rendered === false) {
18353 return false;
18354 }
18355
18356 row.child(rendered, 'child').show();
18357 return true;
18358 }
18359 else {
18360 row.child(false);
18361
18362 return false;
18363 }
18364 }
18365 },
18366
18367 childRowImmediate: function (row, update, render) {
18368 var rowNode = $(row.node());
18369
18370 if (
18371 (!update && rowNode.hasClass('dtr-expanded')) ||
18372 !row.responsive.hasHidden()
18373 ) {
18374 // User interaction and the row is show, or nothing to show
18375 row.child(false);
18376
18377 return false;
18378 }
18379 else {
18380 // Display
18381 var rendered = render();
18382
18383 if (rendered === false) {
18384 return false;
18385 }
18386
18387 row.child(rendered, 'child').show();
18388
18389 return true;
18390 }
18391 },
18392
18393 // This is a wrapper so the modal options for Bootstrap and jQuery UI can
18394 // have options passed into them. This specific one doesn't need to be a
18395 // function but it is for consistency in the `modal` name
18396 modal: function (options) {
18397 return function (row, update, render, closeCallback) {
18398 var modal;
18399 var rendered = render();
18400
18401 if (rendered === false) {
18402 return false;
18403 }
18404
18405 if (!update) {
18406 // Show a modal
18407 var close = function () {
18408 modal.remove(); // will tidy events for us
18409 $(document).off('keypress.dtr');
18410 $(row.node()).removeClass('dtr-expanded');
18411
18412 closeCallback();
18413 };
18414
18415 modal = $('<div class="dtr-modal"/>')
18416 .append(
18417 $('<div class="dtr-modal-display"/>')
18418 .append(
18419 $('<div class="dtr-modal-content"/>')
18420 .data('dtr-row-idx', row.index())
18421 .append(rendered)
18422 )
18423 .append(
18424 $(
18425 '<div class="dtr-modal-close">&times;</div>'
18426 ).click(function () {
18427 close();
18428 })
18429 )
18430 )
18431 .append(
18432 $('<div class="dtr-modal-background"/>').click(
18433 function () {
18434 close();
18435 }
18436 )
18437 )
18438 .appendTo('body');
18439
18440 $(row.node()).addClass('dtr-expanded');
18441
18442 $(document).on('keyup.dtr', function (e) {
18443 if (e.keyCode === 27) {
18444 e.stopPropagation();
18445
18446 close();
18447 }
18448 });
18449 }
18450 else {
18451 modal = $('div.dtr-modal-content');
18452
18453 if (modal.length && row.index() === modal.data('dtr-row-idx')) {
18454 modal.empty().append(rendered);
18455 }
18456 else {
18457 // Modal not shown, nothing to update
18458 return null;
18459 }
18460 }
18461
18462 if (options && options.header) {
18463 $('div.dtr-modal-content').prepend(
18464 '<h2>' + options.header(row) + '</h2>'
18465 );
18466 }
18467
18468 return true;
18469 };
18470 }
18471 };
18472
18473 /**
18474 * Display methods - functions which define how the hidden data should be shown
18475 * in the table.
18476 *
18477 * @namespace
18478 * @name Responsive.defaults
18479 * @static
18480 */
18481 Responsive.renderer = {
18482 listHiddenNodes: function () {
18483 var fn = function (api, rowIdx, columns) {
18484 var that = this;
18485 var ul = $(
18486 '<ul data-dtr-index="' + rowIdx + '" class="dtr-details"/>'
18487 );
18488 var found = false;
18489
18490 $.each(columns, function (i, col) {
18491 if (col.hidden) {
18492 var klass = col.className
18493 ? 'class="' + col.className + '"'
18494 : '';
18495
18496 $(
18497 '<li ' +
18498 klass +
18499 ' data-dtr-index="' +
18500 col.columnIndex +
18501 '" data-dt-row="' +
18502 col.rowIndex +
18503 '" data-dt-column="' +
18504 col.columnIndex +
18505 '">' +
18506 '<span class="dtr-title">' +
18507 col.title +
18508 '</span> ' +
18509 '</li>'
18510 )
18511 .append(
18512 $('<span class="dtr-data"/>').append(
18513 that._childNodes(
18514 api,
18515 col.rowIndex,
18516 col.columnIndex
18517 )
18518 )
18519 ) // api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
18520 .appendTo(ul);
18521
18522 found = true;
18523 }
18524 });
18525
18526 return found ? ul : false;
18527 };
18528
18529 fn._responsiveMovesNodes = true;
18530
18531 return fn;
18532 },
18533
18534 listHidden: function () {
18535 return function (api, rowIdx, columns) {
18536 var data = $.map(columns, function (col) {
18537 var klass = col.className
18538 ? 'class="' + col.className + '"'
18539 : '';
18540
18541 return col.hidden
18542 ? '<li ' +
18543 klass +
18544 ' data-dtr-index="' +
18545 col.columnIndex +
18546 '" data-dt-row="' +
18547 col.rowIndex +
18548 '" data-dt-column="' +
18549 col.columnIndex +
18550 '">' +
18551 '<span class="dtr-title">' +
18552 col.title +
18553 '</span> ' +
18554 '<span class="dtr-data">' +
18555 col.data +
18556 '</span>' +
18557 '</li>'
18558 : '';
18559 }).join('');
18560
18561 return data
18562 ? $(
18563 '<ul data-dtr-index="' +
18564 rowIdx +
18565 '" class="dtr-details"/>'
18566 ).append(data)
18567 : false;
18568 };
18569 },
18570
18571 tableAll: function (options) {
18572 options = $.extend(
18573 {
18574 tableClass: ''
18575 },
18576 options
18577 );
18578
18579 return function (api, rowIdx, columns) {
18580 var data = $.map(columns, function (col) {
18581 var klass = col.className
18582 ? 'class="' + col.className + '"'
18583 : '';
18584
18585 return (
18586 '<tr ' +
18587 klass +
18588 ' data-dt-row="' +
18589 col.rowIndex +
18590 '" data-dt-column="' +
18591 col.columnIndex +
18592 '">' +
18593 '<td>' +
18594 ( '' !== col.title
18595 ? col.title + ':'
18596 : ''
18597 ) +
18598 '</td> ' +
18599 '<td>' +
18600 col.data +
18601 '</td>' +
18602 '</tr>'
18603 );
18604 }).join('');
18605
18606 return $(
18607 '<table class="' +
18608 options.tableClass +
18609 ' dtr-details" width="100%"/>'
18610 ).append(data);
18611 };
18612 }
18613 };
18614
18615 /**
18616 * Responsive default settings for initialisation
18617 *
18618 * @namespace
18619 * @name Responsive.defaults
18620 * @static
18621 */
18622 Responsive.defaults = {
18623 /**
18624 * List of breakpoints for the instance. Note that this means that each
18625 * instance can have its own breakpoints. Additionally, the breakpoints
18626 * cannot be changed once an instance has been creased.
18627 *
18628 * @type {Array}
18629 * @default Takes the value of `Responsive.breakpoints`
18630 */
18631 breakpoints: Responsive.breakpoints,
18632
18633 /**
18634 * Enable / disable auto hiding calculations. It can help to increase
18635 * performance slightly if you disable this option, but all columns would
18636 * need to have breakpoint classes assigned to them
18637 *
18638 * @type {Boolean}
18639 * @default `true`
18640 */
18641 auto: true,
18642
18643 /**
18644 * Details control. If given as a string value, the `type` property of the
18645 * default object is set to that value, and the defaults used for the rest
18646 * of the object - this is for ease of implementation.
18647 *
18648 * The object consists of the following properties:
18649 *
18650 * * `display` - A function that is used to show and hide the hidden details
18651 * * `renderer` - function that is called for display of the child row data.
18652 * The default function will show the data from the hidden columns
18653 * * `target` - Used as the selector for what objects to attach the child
18654 * open / close to
18655 * * `type` - `false` to disable the details display, `inline` or `column`
18656 * for the two control types
18657 *
18658 * @type {Object|string}
18659 */
18660 details: {
18661 display: Responsive.display.childRow,
18662
18663 renderer: Responsive.renderer.listHidden(),
18664
18665 target: 0,
18666
18667 type: 'inline'
18668 },
18669
18670 /**
18671 * Orthogonal data request option. This is used to define the data type
18672 * requested when Responsive gets the data to show in the child row.
18673 *
18674 * @type {String}
18675 */
18676 orthogonal: 'display'
18677 };
18678
18679 /*
18680 * API
18681 */
18682 var Api = $.fn.dataTable.Api;
18683
18684 // Doesn't do anything - work around for a bug in DT... Not documented
18685 Api.register('responsive()', function () {
18686 return this;
18687 });
18688
18689 Api.register('responsive.index()', function (li) {
18690 li = $(li);
18691
18692 return {
18693 column: li.data('dtr-index'),
18694 row: li.parent().data('dtr-index')
18695 };
18696 });
18697
18698 Api.register('responsive.rebuild()', function () {
18699 return this.iterator('table', function (ctx) {
18700 if (ctx._responsive) {
18701 ctx._responsive._classLogic();
18702 }
18703 });
18704 });
18705
18706 Api.register('responsive.recalc()', function () {
18707 return this.iterator('table', function (ctx) {
18708 if (ctx._responsive) {
18709 ctx._responsive._resizeAuto();
18710 ctx._responsive._resize();
18711 }
18712 });
18713 });
18714
18715 Api.register('responsive.hasHidden()', function () {
18716 var ctx = this.context[0];
18717
18718 return ctx._responsive
18719 ? $.inArray(false, ctx._responsive._responsiveOnlyHidden()) !== -1
18720 : false;
18721 });
18722
18723 Api.registerPlural(
18724 'columns().responsiveHidden()',
18725 'column().responsiveHidden()',
18726 function () {
18727 return this.iterator(
18728 'column',
18729 function (settings, column) {
18730 return settings._responsive
18731 ? settings._responsive._responsiveOnlyHidden()[column]
18732 : false;
18733 },
18734 1
18735 );
18736 }
18737 );
18738
18739 /**
18740 * Version information
18741 *
18742 * @name Responsive.version
18743 * @static
18744 */
18745 Responsive.version = '3.0.4';
18746
18747 $.fn.dataTable.Responsive = Responsive;
18748 $.fn.DataTable.Responsive = Responsive;
18749
18750 // Attach a listener to the document which listens for DataTables initialisation
18751 // events so we can automatically initialise
18752 $(document).on('preInit.dt.dtr', function (e, settings, json) {
18753 if (e.namespace !== 'dt') {
18754 return;
18755 }
18756
18757 if (
18758 $(settings.nTable).hasClass('responsive') ||
18759 $(settings.nTable).hasClass('dt-responsive') ||
18760 settings.oInit.responsive ||
18761 DataTable.defaults.responsive
18762 ) {
18763 var init = settings.oInit.responsive;
18764
18765 if (init !== false) {
18766 new Responsive(settings, $.isPlainObject(init) ? init : {});
18767 }
18768 }
18769 });
18770
18771
18772 return DataTable;
18773 }));
18774
18775
18776 /*! DataTables styling wrapper for Responsive
18777 * © SpryMedia Ltd - datatables.net/license
18778 */
18779
18780 (function( factory ){
18781 if ( typeof define === 'function' && define.amd ) {
18782 // AMD
18783 define( ['jquery', 'datatables.net-dt', 'datatables.net-responsive'], function ( $ ) {
18784 return factory( $, window, document );
18785 } );
18786 }
18787 else if ( typeof exports === 'object' ) {
18788 // CommonJS
18789 var jq = require('jquery');
18790 var cjsRequires = function (root, $) {
18791 if ( ! $.fn.dataTable ) {
18792 require('datatables.net-dt')(root, $);
18793 }
18794
18795 if ( ! $.fn.dataTable.Responsive ) {
18796 require('datatables.net-responsive')(root, $);
18797 }
18798 };
18799
18800 if (typeof window === 'undefined') {
18801 module.exports = function (root, $) {
18802 if ( ! root ) {
18803 // CommonJS environments without a window global must pass a
18804 // root. This will give an error otherwise
18805 root = window;
18806 }
18807
18808 if ( ! $ ) {
18809 $ = jq( root );
18810 }
18811
18812 cjsRequires( root, $ );
18813 return factory( $, root, root.document );
18814 };
18815 }
18816 else {
18817 cjsRequires( window, jq );
18818 module.exports = factory( jq, window, window.document );
18819 }
18820 }
18821 else {
18822 // Browser
18823 factory( jQuery, window, document );
18824 }
18825 }(function( $, window, document ) {
18826 'use strict';
18827 var DataTable = $.fn.dataTable;
18828
18829
18830
18831
18832 return DataTable;
18833 }));
18834
18835
18836