GPQAPP
1/*! Responsive 2.2.9
2* 2014-2021 SpryMedia Ltd - datatables.net/license
3*/
4
5/**
6* @summary Responsive
7* @description Responsive tables plug-in for DataTables
8* @version 2.2.9
9* @file dataTables.responsive.js
10* @author SpryMedia Ltd (www.sprymedia.co.uk)
11* @contact www.sprymedia.co.uk/contact
12* @copyright Copyright 2014-2021 SpryMedia Ltd.
13*
14* This source file is free software, available under the following license:
15* MIT license - http://datatables.net/license/mit
16*
17* This source file is distributed in the hope that it will be useful, but
18* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
20*
21* For details please refer to: http://www.datatables.net
22*/
23(function( factory ){24if ( typeof define === 'function' && define.amd ) {25// AMD26define( ['jquery', 'datatables.net'], function ( $ ) {27return factory( $, window, document );28} );29}30else if ( typeof exports === 'object' ) {31// CommonJS32module.exports = function (root, $) {33if ( ! root ) {34root = window;35}36
37if ( ! $ || ! $.fn.dataTable ) {38$ = require('datatables.net')(root, $).$;39}40
41return factory( $, root, root.document );42};43}44else {45// Browser46factory( jQuery, window, document );47}48}(function( $, window, document, undefined ) {49'use strict';50var DataTable = $.fn.dataTable;51
52
53/**
54* Responsive is a plug-in for the DataTables library that makes use of
55* DataTables' ability to change the visibility of columns, changing the
56* visibility of columns so the displayed columns fit into the table container.
57* The end result is that complex tables will be dynamically adjusted to fit
58* into the viewport, be it on a desktop, tablet or mobile browser.
59*
60* Responsive for DataTables has two modes of operation, which can used
61* individually or combined:
62*
63* * Class name based control - columns assigned class names that match the
64* breakpoint logic can be shown / hidden as required for each breakpoint.
65* * Automatic control - columns are automatically hidden when there is no
66* room left to display them. Columns removed from the right.
67*
68* In additional to column visibility control, Responsive also has built into
69* options to use DataTables' child row display to show / hide the information
70* from the table that has been hidden. There are also two modes of operation
71* for this child row display:
72*
73* * Inline - when the control element that the user can use to show / hide
74* child rows is displayed inside the first column of the table.
75* * Column - where a whole column is dedicated to be the show / hide control.
76*
77* Initialisation of Responsive is performed by:
78*
79* * Adding the class `responsive` or `dt-responsive` to the table. In this case
80* Responsive will automatically be initialised with the default configuration
81* options when the DataTable is created.
82* * Using the `responsive` option in the DataTables configuration options. This
83* can also be used to specify the configuration options, or simply set to
84* `true` to use the defaults.
85*
86* @class
87* @param {object} settings DataTables settings object for the host table
88* @param {object} [opts] Configuration options
89* @requires jQuery 1.7+
90* @requires DataTables 1.10.3+
91*
92* @example
93* $('#example').DataTable( {
94* responsive: true
95* } );
96* } );
97*/
98var Responsive = function ( settings, opts ) {99// Sanity check that we are using DataTables 1.10 or newer100if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.10' ) ) {101throw 'DataTables Responsive requires DataTables 1.10.10 or newer';102}103
104this.s = {105dt: new DataTable.Api( settings ),106columns: [],107current: []108};109
110// Check if responsive has already been initialised on this table111if ( this.s.dt.settings()[0].responsive ) {112return;113}114
115// details is an object, but for simplicity the user can give it as a string116// or a boolean117if ( opts && typeof opts.details === 'string' ) {118opts.details = { type: opts.details };119}120else if ( opts && opts.details === false ) {121opts.details = { type: false };122}123else if ( opts && opts.details === true ) {124opts.details = { type: 'inline' };125}126
127this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );128settings.responsive = this;129this._constructor();130};131
132$.extend( Responsive.prototype, {133/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *134* Constructor
135*/
136
137/**138* Initialise the Responsive instance
139*
140* @private
141*/
142_constructor: function ()143{144var that = this;145var dt = this.s.dt;146var dtPrivateSettings = dt.settings()[0];147var oldWindowWidth = $(window).innerWidth();148
149dt.settings()[0]._responsive = this;150
151// Use DataTables' throttle function to avoid processor thrashing on152// resize153$(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {154// iOS has a bug whereby resize can fire when only scrolling155// See: http://stackoverflow.com/questions/8898412156var width = $(window).innerWidth();157
158if ( width !== oldWindowWidth ) {159that._resize();160oldWindowWidth = width;161}162} ) );163
164// DataTables doesn't currently trigger an event when a row is added, so165// we need to hook into its private API to enforce the hidden rows when166// new data is added167dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {168if ( $.inArray( false, that.s.current ) !== -1 ) {169$('>td, >th', tr).each( function ( i ) {170var idx = dt.column.index( 'toData', i );171
172if ( that.s.current[idx] === false ) {173$(this).css('display', 'none');174}175} );176}177} );178
179// Destroy event handler180dt.on( 'destroy.dtr', function () {181dt.off( '.dtr' );182$( dt.table().body() ).off( '.dtr' );183$(window).off( 'resize.dtr orientationchange.dtr' );184dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control');185
186// Restore the columns that we've hidden187$.each( that.s.current, function ( i, val ) {188if ( val === false ) {189that._setColumnVis( i, true );190}191} );192} );193
194// Reorder the breakpoints array here in case they have been added out195// of order196this.c.breakpoints.sort( function (a, b) {197return a.width < b.width ? 1 :198a.width > b.width ? -1 : 0;199} );200
201this._classLogic();202this._resizeAuto();203
204// Details handler205var details = this.c.details;206
207if ( details.type !== false ) {208that._detailsInit();209
210// DataTables will trigger this event on every column it shows and211// hides individually212dt.on( 'column-visibility.dtr', function () {213// Use a small debounce to allow multiple columns to be set together214if ( that._timer ) {215clearTimeout( that._timer );216}217
218that._timer = setTimeout( function () {219that._timer = null;220
221that._classLogic();222that._resizeAuto();223that._resize(true);224
225that._redrawChildren();226}, 100 );227} );228
229// Redraw the details box on each draw which will happen if the data230// has changed. This is used until DataTables implements a native231// `updated` event for rows232dt.on( 'draw.dtr', function () {233that._redrawChildren();234} );235
236$(dt.table().node()).addClass( 'dtr-'+details.type );237}238
239dt.on( 'column-reorder.dtr', function (e, settings, details) {240that._classLogic();241that._resizeAuto();242that._resize(true);243} );244
245// Change in column sizes means we need to calc246dt.on( 'column-sizing.dtr', function () {247that._resizeAuto();248that._resize();249});250
251// On Ajax reload we want to reopen any child rows which are displayed252// by responsive253dt.on( 'preXhr.dtr', function () {254var rowIds = [];255dt.rows().every( function () {256if ( this.child.isShown() ) {257rowIds.push( this.id(true) );258}259} );260
261dt.one( 'draw.dtr', function () {262that._resizeAuto();263that._resize();264
265dt.rows( rowIds ).every( function () {266that._detailsDisplay( this, false );267} );268} );269});270
271dt
272.on( 'draw.dtr', function () {273that._controlClass();274})275.on( 'init.dtr', function (e, settings, details) {276if ( e.namespace !== 'dt' ) {277return;278}279
280that._resizeAuto();281that._resize();282
283// If columns were hidden, then DataTables needs to adjust the284// column sizing285if ( $.inArray( false, that.s.current ) ) {286dt.columns.adjust();287}288} );289
290// First pass - draw the table for the current viewport size291this._resize();292},293
294
295/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *296* Private methods
297*/
298
299/**300* Calculate the visibility for the columns in a table for a given
301* breakpoint. The result is pre-determined based on the class logic if
302* class names are used to control all columns, but the width of the table
303* is also used if there are columns which are to be automatically shown
304* and hidden.
305*
306* @param {string} breakpoint Breakpoint name to use for the calculation
307* @return {array} Array of boolean values initiating the visibility of each
308* column.
309* @private
310*/
311_columnsVisiblity: function ( breakpoint )312{313var dt = this.s.dt;314var columns = this.s.columns;315var i, ien;316
317// Create an array that defines the column ordering based first on the318// column's priority, and secondly the column index. This allows the319// columns to be removed from the right if the priority matches320var order = columns321.map( function ( col, idx ) {322return {323columnIdx: idx,324priority: col.priority325};326} )327.sort( function ( a, b ) {328if ( a.priority !== b.priority ) {329return a.priority - b.priority;330}331return a.columnIdx - b.columnIdx;332} );333
334// Class logic - determine which columns are in this breakpoint based335// on the classes. If no class control (i.e. `auto`) then `-` is used336// to indicate this to the rest of the function337var display = $.map( columns, function ( col, i ) {338if ( dt.column(i).visible() === false ) {339return 'not-visible';340}341return col.auto && col.minWidth === null ?342false :343col.auto === true ?344'-' :345$.inArray( breakpoint, col.includeIn ) !== -1;346} );347
348// Auto column control - first pass: how much width is taken by the349// ones that must be included from the non-auto columns350var requiredWidth = 0;351for ( i=0, ien=display.length ; i<ien ; i++ ) {352if ( display[i] === true ) {353requiredWidth += columns[i].minWidth;354}355}356
357// Second pass, use up any remaining width for other columns. For358// scrolling tables we need to subtract the width of the scrollbar. It359// may not be requires which makes this sub-optimal, but it would360// require another full redraw to make complete use of those extra few361// pixels362var scrolling = dt.settings()[0].oScroll;363var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;364var widthAvailable = dt.table().container().offsetWidth - bar;365var usedWidth = widthAvailable - requiredWidth;366
367// Control column needs to always be included. This makes it sub-368// optimal in terms of using the available with, but to stop layout369// thrashing or overflow. Also we need to account for the control column370// width first so we know how much width is available for the other371// columns, since the control column might not be the first one shown372for ( i=0, ien=display.length ; i<ien ; i++ ) {373if ( columns[i].control ) {374usedWidth -= columns[i].minWidth;375}376}377
378// Allow columns to be shown (counting by priority and then right to379// left) until we run out of room380var empty = false;381for ( i=0, ien=order.length ; i<ien ; i++ ) {382var colIdx = order[i].columnIdx;383
384if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {385// Once we've found a column that won't fit we don't let any386// others display either, or columns might disappear in the387// middle of the table388if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {389empty = true;390display[colIdx] = false;391}392else {393display[colIdx] = true;394}395
396usedWidth -= columns[colIdx].minWidth;397}398}399
400// Determine if the 'control' column should be shown (if there is one).401// This is the case when there is a hidden column (that is not the402// control column). The two loops look inefficient here, but they are403// trivial and will fly through. We need to know the outcome from the404// first , before the action in the second can be taken405var showControl = false;406
407for ( i=0, ien=columns.length ; i<ien ; i++ ) {408if ( ! columns[i].control && ! columns[i].never && display[i] === false ) {409showControl = true;410break;411}412}413
414for ( i=0, ien=columns.length ; i<ien ; i++ ) {415if ( columns[i].control ) {416display[i] = showControl;417}418
419// Replace not visible string with false from the control column detection above420if ( display[i] === 'not-visible' ) {421display[i] = false;422}423}424
425// Finally we need to make sure that there is at least one column that426// is visible427if ( $.inArray( true, display ) === -1 ) {428display[0] = true;429}430
431return display;432},433
434
435/**436* Create the internal `columns` array with information about the columns
437* for the table. This includes determining which breakpoints the column
438* will appear in, based upon class names in the column, which makes up the
439* vast majority of this method.
440*
441* @private
442*/
443_classLogic: function ()444{445var that = this;446var calc = {};447var breakpoints = this.c.breakpoints;448var dt = this.s.dt;449var columns = dt.columns().eq(0).map( function (i) {450var column = this.column(i);451var className = column.header().className;452var priority = dt.settings()[0].aoColumns[i].responsivePriority;453var dataPriority = column.header().getAttribute('data-priority');454
455if ( priority === undefined ) {456priority = dataPriority === undefined || dataPriority === null?45710000 :458dataPriority * 1;459}460
461return {462className: className,463includeIn: [],464auto: false,465control: false,466never: className.match(/\bnever\b/) ? true : false,467priority: priority468};469} );470
471// Simply add a breakpoint to `includeIn` array, ensuring that there are472// no duplicates473var add = function ( colIdx, name ) {474var includeIn = columns[ colIdx ].includeIn;475
476if ( $.inArray( name, includeIn ) === -1 ) {477includeIn.push( name );478}479};480
481var column = function ( colIdx, name, operator, matched ) {482var size, i, ien;483
484if ( ! operator ) {485columns[ colIdx ].includeIn.push( name );486}487else if ( operator === 'max-' ) {488// Add this breakpoint and all smaller489size = that._find( name ).width;490
491for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {492if ( breakpoints[i].width <= size ) {493add( colIdx, breakpoints[i].name );494}495}496}497else if ( operator === 'min-' ) {498// Add this breakpoint and all larger499size = that._find( name ).width;500
501for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {502if ( breakpoints[i].width >= size ) {503add( colIdx, breakpoints[i].name );504}505}506}507else if ( operator === 'not-' ) {508// Add all but this breakpoint509for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {510if ( breakpoints[i].name.indexOf( matched ) === -1 ) {511add( colIdx, breakpoints[i].name );512}513}514}515};516
517// Loop over each column and determine if it has a responsive control518// class519columns.each( function ( col, i ) {520var classNames = col.className.split(' ');521var hasClass = false;522
523// Split the class name up so multiple rules can be applied if needed524for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {525var className = classNames[k].trim();526
527if ( className === 'all' ) {528// Include in all529hasClass = true;530col.includeIn = $.map( breakpoints, function (a) {531return a.name;532} );533return;534}535else if ( className === 'none' || col.never ) {536// Include in none (default) and no auto537hasClass = true;538return;539}540else if ( className === 'control' || className === 'dtr-control' ) {541// Special column that is only visible, when one of the other542// columns is hidden. This is used for the details control543hasClass = true;544col.control = true;545return;546}547
548$.each( breakpoints, function ( j, breakpoint ) {549// Does this column have a class that matches this breakpoint?550var brokenPoint = breakpoint.name.split('-');551var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );552var match = className.match( re );553
554if ( match ) {555hasClass = true;556
557if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {558// Class name matches breakpoint name fully559column( i, breakpoint.name, match[1], match[2]+match[3] );560}561else if ( match[2] === brokenPoint[0] && ! match[3] ) {562// Class name matched primary breakpoint name with no qualifier563column( i, breakpoint.name, match[1], match[2] );564}565}566} );567}568
569// If there was no control class, then automatic sizing is used570if ( ! hasClass ) {571col.auto = true;572}573} );574
575this.s.columns = columns;576},577
578/**579* Update the cells to show the correct control class / button
580* @private
581*/
582_controlClass: function ()583{584if ( this.c.details.type === 'inline' ) {585var dt = this.s.dt;586var columnsVis = this.s.current;587var firstVisible = $.inArray(true, columnsVis);588
589// Remove from any cells which shouldn't have it590dt.cells(591null,592function(idx) {593return idx !== firstVisible;594},595{page: 'current'}596)597.nodes()598.to$()599.filter('.dtr-control')600.removeClass('dtr-control');601
602dt.cells(null, firstVisible, {page: 'current'})603.nodes()604.to$()605.addClass('dtr-control');606}607},608
609/**610* Show the details for the child row
611*
612* @param {DataTables.Api} row API instance for the row
613* @param {boolean} update Update flag
614* @private
615*/
616_detailsDisplay: function ( row, update )617{618var that = this;619var dt = this.s.dt;620var details = this.c.details;621
622if ( details && details.type !== false ) {623var res = details.display( row, update, function () {624return details.renderer(625dt, row[0], that._detailsObj(row[0])626);627} );628
629if ( res === true || res === false ) {630$(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );631}632}633},634
635
636/**637* Initialisation for the details handler
638*
639* @private
640*/
641_detailsInit: function ()642{643var that = this;644var dt = this.s.dt;645var details = this.c.details;646
647// The inline type always uses the first child as the target648if ( details.type === 'inline' ) {649details.target = 'td.dtr-control, th.dtr-control';650}651
652// Keyboard accessibility653dt.on( 'draw.dtr', function () {654that._tabIndexes();655} );656that._tabIndexes(); // Initial draw has already happened657
658$( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {659if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {660$(this).click();661}662} );663
664// type.target can be a string jQuery selector or a column index665var target = details.target;666var selector = typeof target === 'string' ? target : 'td, th';667
668if ( target !== undefined || target !== null ) {669// Click handler to show / hide the details rows when they are available670$( dt.table().body() )671.on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {672// If the table is not collapsed (i.e. there is no hidden columns)673// then take no action674if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {675return;676}677
678// Check that the row is actually a DataTable's controlled node679if ( $.inArray( $(this).closest('tr').get(0), dt.rows().nodes().toArray() ) === -1 ) {680return;681}682
683// For column index, we determine if we should act or not in the684// handler - otherwise it is already okay685if ( typeof target === 'number' ) {686var targetIdx = target < 0 ?687dt.columns().eq(0).length + target :688target;689
690if ( dt.cell( this ).index().column !== targetIdx ) {691return;692}693}694
695// $().closest() includes itself in its check696var row = dt.row( $(this).closest('tr') );697
698// Check event type to do an action699if ( e.type === 'click' ) {700// The renderer is given as a function so the caller can execute it701// only when they need (i.e. if hiding there is no point is running702// the renderer)703that._detailsDisplay( row, false );704}705else if ( e.type === 'mousedown' ) {706// For mouse users, prevent the focus ring from showing707$(this).css('outline', 'none');708}709else if ( e.type === 'mouseup' ) {710// And then re-allow at the end of the click711$(this).trigger('blur').css('outline', '');712}713} );714}715},716
717
718/**719* Get the details to pass to a renderer for a row
720* @param {int} rowIdx Row index
721* @private
722*/
723_detailsObj: function ( rowIdx )724{725var that = this;726var dt = this.s.dt;727
728return $.map( this.s.columns, function( col, i ) {729// Never and control columns should not be passed to the renderer730if ( col.never || col.control ) {731return;732}733
734var dtCol = dt.settings()[0].aoColumns[ i ];735
736return {737className: dtCol.sClass,738columnIndex: i,739data: dt.cell( rowIdx, i ).render( that.c.orthogonal ),740hidden: dt.column( i ).visible() && !that.s.current[ i ],741rowIndex: rowIdx,742title: dtCol.sTitle !== null ?743dtCol.sTitle :744$(dt.column(i).header()).text()745};746} );747},748
749
750/**751* Find a breakpoint object from a name
752*
753* @param {string} name Breakpoint name to find
754* @return {object} Breakpoint description object
755* @private
756*/
757_find: function ( name )758{759var breakpoints = this.c.breakpoints;760
761for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {762if ( breakpoints[i].name === name ) {763return breakpoints[i];764}765}766},767
768
769/**770* Re-create the contents of the child rows as the display has changed in
771* some way.
772*
773* @private
774*/
775_redrawChildren: function ()776{777var that = this;778var dt = this.s.dt;779
780dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {781var row = dt.row( idx );782
783that._detailsDisplay( dt.row( idx ), true );784} );785},786
787
788/**789* Alter the table display for a resized viewport. This involves first
790* determining what breakpoint the window currently is in, getting the
791* column visibilities to apply and then setting them.
792*
793* @param {boolean} forceRedraw Force a redraw
794* @private
795*/
796_resize: function (forceRedraw)797{798var that = this;799var dt = this.s.dt;800var width = $(window).innerWidth();801var breakpoints = this.c.breakpoints;802var breakpoint = breakpoints[0].name;803var columns = this.s.columns;804var i, ien;805var oldVis = this.s.current.slice();806
807// Determine what breakpoint we are currently at808for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {809if ( width <= breakpoints[i].width ) {810breakpoint = breakpoints[i].name;811break;812}813}814
815// Show the columns for that break point816var columnsVis = this._columnsVisiblity( breakpoint );817this.s.current = columnsVis;818
819// Set the class before the column visibility is changed so event820// listeners know what the state is. Need to determine if there are821// any columns that are not visible but can be shown822var collapsedClass = false;823
824for ( i=0, ien=columns.length ; i<ien ; i++ ) {825if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control && ! dt.column(i).visible() === false ) {826collapsedClass = true;827break;828}829}830
831$( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );832
833var changed = false;834var visible = 0;835
836dt.columns().eq(0).each( function ( colIdx, i ) {837if ( columnsVis[i] === true ) {838visible++;839}840
841if ( forceRedraw || columnsVis[i] !== oldVis[i] ) {842changed = true;843that._setColumnVis( colIdx, columnsVis[i] );844}845} );846
847if ( changed ) {848this._redrawChildren();849
850// Inform listeners of the change851$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );852
853// If no records, update the "No records" display element854if ( dt.page.info().recordsDisplay === 0 ) {855$('td', dt.table().body()).eq(0).attr('colspan', visible);856}857}858
859that._controlClass();860},861
862
863/**864* Determine the width of each column in the table so the auto column hiding
865* has that information to work with. This method is never going to be 100%
866* perfect since column widths can change slightly per page, but without
867* seriously compromising performance this is quite effective.
868*
869* @private
870*/
871_resizeAuto: function ()872{873var dt = this.s.dt;874var columns = this.s.columns;875
876// Are we allowed to do auto sizing?877if ( ! this.c.auto ) {878return;879}880
881// Are there any columns that actually need auto-sizing, or do they all882// have classes defined883if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {884return;885}886
887// Need to restore all children. They will be reinstated by a re-render888if ( ! $.isEmptyObject( _childNodeStore ) ) {889$.each( _childNodeStore, function ( key ) {890var idx = key.split('-');891
892_childNodesRestore( dt, idx[0]*1, idx[1]*1 );893} );894}895
896// Clone the table with the current data in it897var tableWidth = dt.table().node().offsetWidth;898var columnWidths = dt.columns;899var clonedTable = dt.table().node().cloneNode( false );900var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );901var clonedBody = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8902
903clonedTable.style.width = 'auto';904
905// Header906var headerCells = dt.columns()907.header()908.filter( function (idx) {909return dt.column(idx).visible();910} )911.to$()912.clone( false )913.css( 'display', 'table-cell' )914.css( 'width', 'auto' )915.css( 'min-width', 0 );916
917// Body rows - we don't need to take account of DataTables' column918// visibility since we implement our own here (hence the `display` set)919$(clonedBody)920.append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )921.find( 'th, td' ).css( 'display', '' );922
923// Footer924var footer = dt.table().footer();925if ( footer ) {926var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );927var footerCells = dt.columns()928.footer()929.filter( function (idx) {930return dt.column(idx).visible();931} )932.to$()933.clone( false )934.css( 'display', 'table-cell' );935
936$('<tr/>')937.append( footerCells )938.appendTo( clonedFooter );939}940
941$('<tr/>')942.append( headerCells )943.appendTo( clonedHeader );944
945// In the inline case extra padding is applied to the first column to946// give space for the show / hide icon. We need to use this in the947// calculation948if ( this.c.details.type === 'inline' ) {949$(clonedTable).addClass( 'dtr-inline collapsed' );950}951
952// It is unsafe to insert elements with the same name into the DOM953// multiple times. For example, cloning and inserting a checked radio954// clears the chcecked state of the original radio.955$( clonedTable ).find( '[name]' ).removeAttr( 'name' );956
957// A position absolute table would take the table out of the flow of958// our container element, bypassing the height and width (Scroller)959$( clonedTable ).css( 'position', 'relative' )960
961var inserted = $('<div/>')962.css( {963width: 1,964height: 1,965overflow: 'hidden',966clear: 'both'967} )968.append( clonedTable );969
970inserted.insertBefore( dt.table().node() );971
972// The cloned header now contains the smallest that each column can be973headerCells.each( function (i) {974var idx = dt.column.index( 'fromVisible', i );975columns[ idx ].minWidth = this.offsetWidth || 0;976} );977
978inserted.remove();979},980
981/**982* Get the state of the current hidden columns - controlled by Responsive only
983*/
984_responsiveOnlyHidden: function ()985{986var dt = this.s.dt;987
988return $.map( this.s.current, function (v, i) {989// If the column is hidden by DataTables then it can't be hidden by990// Responsive!991if ( dt.column(i).visible() === false ) {992return true;993}994return v;995} );996},997
998/**999* Set a column's visibility.
1000*
1001* We don't use DataTables' column visibility controls in order to ensure
1002* that column visibility can Responsive can no-exist. Since only IE8+ is
1003* supported (and all evergreen browsers of course) the control of the
1004* display attribute works well.
1005*
1006* @param {integer} col Column index
1007* @param {boolean} showHide Show or hide (true or false)
1008* @private
1009*/
1010_setColumnVis: function ( col, showHide )1011{1012var dt = this.s.dt;1013var display = showHide ? '' : 'none'; // empty string will remove the attr1014
1015$( dt.column( col ).header() ).css( 'display', display );1016$( dt.column( col ).footer() ).css( 'display', display );1017dt.column( col ).nodes().to$().css( 'display', display );1018
1019// If the are child nodes stored, we might need to reinsert them1020if ( ! $.isEmptyObject( _childNodeStore ) ) {1021dt.cells( null, col ).indexes().each( function (idx) {1022_childNodesRestore( dt, idx.row, idx.column );1023} );1024}1025},1026
1027
1028/**1029* Update the cell tab indexes for keyboard accessibility. This is called on
1030* every table draw - that is potentially inefficient, but also the least
1031* complex option given that column visibility can change on the fly. Its a
1032* shame user-focus was removed from CSS 3 UI, as it would have solved this
1033* issue with a single CSS statement.
1034*
1035* @private
1036*/
1037_tabIndexes: function ()1038{1039var dt = this.s.dt;1040var cells = dt.cells( { page: 'current' } ).nodes().to$();1041var ctx = dt.settings()[0];1042var target = this.c.details.target;1043
1044cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );1045
1046if ( typeof target === 'number' ) {1047dt.cells( null, target, { page: 'current' } ).nodes().to$()1048.attr( 'tabIndex', ctx.iTabIndex )1049.data( 'dtr-keyboard', 1 );1050}1051else {1052// This is a bit of a hack - we need to limit the selected nodes to just1053// those of this table1054if ( target === 'td:first-child, th:first-child' ) {1055target = '>td:first-child, >th:first-child';1056}1057
1058$( target, dt.rows( { page: 'current' } ).nodes() )1059.attr( 'tabIndex', ctx.iTabIndex )1060.data( 'dtr-keyboard', 1 );1061}1062}1063} );1064
1065
1066/**
1067* List of default breakpoints. Each item in the array is an object with two
1068* properties:
1069*
1070* * `name` - the breakpoint name.
1071* * `width` - the breakpoint width
1072*
1073* @name Responsive.breakpoints
1074* @static
1075*/
1076Responsive.breakpoints = [1077{ name: 'desktop', width: Infinity },1078{ name: 'tablet-l', width: 1024 },1079{ name: 'tablet-p', width: 768 },1080{ name: 'mobile-l', width: 480 },1081{ name: 'mobile-p', width: 320 }1082];1083
1084
1085/**
1086* Display methods - functions which define how the hidden data should be shown
1087* in the table.
1088*
1089* @namespace
1090* @name Responsive.defaults
1091* @static
1092*/
1093Responsive.display = {1094childRow: function ( row, update, render ) {1095if ( update ) {1096if ( $(row.node()).hasClass('parent') ) {1097row.child( render(), 'child' ).show();1098
1099return true;1100}1101}1102else {1103if ( ! row.child.isShown() ) {1104row.child( render(), 'child' ).show();1105$( row.node() ).addClass( 'parent' );1106
1107return true;1108}1109else {1110row.child( false );1111$( row.node() ).removeClass( 'parent' );1112
1113return false;1114}1115}1116},1117
1118childRowImmediate: function ( row, update, render ) {1119if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {1120// User interaction and the row is show, or nothing to show1121row.child( false );1122$( row.node() ).removeClass( 'parent' );1123
1124return false;1125}1126else {1127// Display1128row.child( render(), 'child' ).show();1129$( row.node() ).addClass( 'parent' );1130
1131return true;1132}1133},1134
1135// This is a wrapper so the modal options for Bootstrap and jQuery UI can1136// have options passed into them. This specific one doesn't need to be a1137// function but it is for consistency in the `modal` name1138modal: function ( options ) {1139return function ( row, update, render ) {1140if ( ! update ) {1141// Show a modal1142var close = function () {1143modal.remove(); // will tidy events for us1144$(document).off( 'keypress.dtr' );1145};1146
1147var modal = $('<div class="dtr-modal"/>')1148.append( $('<div class="dtr-modal-display"/>')1149.append( $('<div class="dtr-modal-content"/>')1150.append( render() )1151)1152.append( $('<div class="dtr-modal-close">×</div>' )1153.click( function () {1154close();1155} )1156)1157)1158.append( $('<div class="dtr-modal-background"/>')1159.click( function () {1160close();1161} )1162)1163.appendTo( 'body' );1164
1165$(document).on( 'keyup.dtr', function (e) {1166if ( e.keyCode === 27 ) {1167e.stopPropagation();1168
1169close();1170}1171} );1172}1173else {1174$('div.dtr-modal-content')1175.empty()1176.append( render() );1177}1178
1179if ( options && options.header ) {1180$('div.dtr-modal-content').prepend(1181'<h2>'+options.header( row )+'</h2>'1182);1183}1184};1185}1186};1187
1188
1189var _childNodeStore = {};1190
1191function _childNodes( dt, row, col ) {1192var name = row+'-'+col;1193
1194if ( _childNodeStore[ name ] ) {1195return _childNodeStore[ name ];1196}1197
1198// https://jsperf.com/childnodes-array-slice-vs-loop1199var nodes = [];1200var children = dt.cell( row, col ).node().childNodes;1201for ( var i=0, ien=children.length ; i<ien ; i++ ) {1202nodes.push( children[i] );1203}1204
1205_childNodeStore[ name ] = nodes;1206
1207return nodes;1208}
1209
1210function _childNodesRestore( dt, row, col ) {1211var name = row+'-'+col;1212
1213if ( ! _childNodeStore[ name ] ) {1214return;1215}1216
1217var node = dt.cell( row, col ).node();1218var store = _childNodeStore[ name ];1219var parent = store[0].parentNode;1220var parentChildren = parent.childNodes;1221var a = [];1222
1223for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {1224a.push( parentChildren[i] );1225}1226
1227for ( var j=0, jen=a.length ; j<jen ; j++ ) {1228node.appendChild( a[j] );1229}1230
1231_childNodeStore[ name ] = undefined;1232}
1233
1234
1235/**
1236* Display methods - functions which define how the hidden data should be shown
1237* in the table.
1238*
1239* @namespace
1240* @name Responsive.defaults
1241* @static
1242*/
1243Responsive.renderer = {1244listHiddenNodes: function () {1245return function ( api, rowIdx, columns ) {1246var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>');1247var found = false;1248
1249var data = $.each( columns, function ( i, col ) {1250if ( col.hidden ) {1251var klass = col.className ?1252'class="'+ col.className +'"' :1253'';1254
1255$(1256'<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+1257'<span class="dtr-title">'+1258col.title+1259'</span> '+1260'</li>'1261)1262.append( $('<span class="dtr-data"/>').append( _childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )1263.appendTo( ul );1264
1265found = true;1266}1267} );1268
1269return found ?1270ul :1271false;1272};1273},1274
1275listHidden: function () {1276return function ( api, rowIdx, columns ) {1277var data = $.map( columns, function ( col ) {1278var klass = col.className ?1279'class="'+ col.className +'"' :1280'';1281
1282return col.hidden ?1283'<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+1284'<span class="dtr-title">'+1285col.title+1286'</span> '+1287'<span class="dtr-data">'+1288col.data+1289'</span>'+1290'</li>' :1291'';1292} ).join('');1293
1294return data ?1295$('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>').append( data ) :1296false;1297}1298},1299
1300tableAll: function ( options ) {1301options = $.extend( {1302tableClass: ''1303}, options );1304
1305return function ( api, rowIdx, columns ) {1306var data = $.map( columns, function ( col ) {1307var klass = col.className ?1308'class="'+ col.className +'"' :1309'';1310
1311return '<tr '+klass+' data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+1312'<td>'+col.title+':'+'</td> '+1313'<td>'+col.data+'</td>'+1314'</tr>';1315} ).join('');1316
1317return $('<table class="'+options.tableClass+' dtr-details" width="100%"/>').append( data );1318}1319}1320};1321
1322/**
1323* Responsive default settings for initialisation
1324*
1325* @namespace
1326* @name Responsive.defaults
1327* @static
1328*/
1329Responsive.defaults = {1330/**1331* List of breakpoints for the instance. Note that this means that each
1332* instance can have its own breakpoints. Additionally, the breakpoints
1333* cannot be changed once an instance has been creased.
1334*
1335* @type {Array}
1336* @default Takes the value of `Responsive.breakpoints`
1337*/
1338breakpoints: Responsive.breakpoints,1339
1340/**1341* Enable / disable auto hiding calculations. It can help to increase
1342* performance slightly if you disable this option, but all columns would
1343* need to have breakpoint classes assigned to them
1344*
1345* @type {Boolean}
1346* @default `true`
1347*/
1348auto: true,1349
1350/**1351* Details control. If given as a string value, the `type` property of the
1352* default object is set to that value, and the defaults used for the rest
1353* of the object - this is for ease of implementation.
1354*
1355* The object consists of the following properties:
1356*
1357* * `display` - A function that is used to show and hide the hidden details
1358* * `renderer` - function that is called for display of the child row data.
1359* The default function will show the data from the hidden columns
1360* * `target` - Used as the selector for what objects to attach the child
1361* open / close to
1362* * `type` - `false` to disable the details display, `inline` or `column`
1363* for the two control types
1364*
1365* @type {Object|string}
1366*/
1367details: {1368display: Responsive.display.childRow,1369
1370renderer: Responsive.renderer.listHidden(),1371
1372target: 0,1373
1374type: 'inline'1375},1376
1377/**1378* Orthogonal data request option. This is used to define the data type
1379* requested when Responsive gets the data to show in the child row.
1380*
1381* @type {String}
1382*/
1383orthogonal: 'display'1384};1385
1386
1387/*
1388* API
1389*/
1390var Api = $.fn.dataTable.Api;1391
1392// Doesn't do anything - work around for a bug in DT... Not documented
1393Api.register( 'responsive()', function () {1394return this;1395} );1396
1397Api.register( 'responsive.index()', function ( li ) {1398li = $(li);1399
1400return {1401column: li.data('dtr-index'),1402row: li.parent().data('dtr-index')1403};1404} );1405
1406Api.register( 'responsive.rebuild()', function () {1407return this.iterator( 'table', function ( ctx ) {1408if ( ctx._responsive ) {1409ctx._responsive._classLogic();1410}1411} );1412} );1413
1414Api.register( 'responsive.recalc()', function () {1415return this.iterator( 'table', function ( ctx ) {1416if ( ctx._responsive ) {1417ctx._responsive._resizeAuto();1418ctx._responsive._resize();1419}1420} );1421} );1422
1423Api.register( 'responsive.hasHidden()', function () {1424var ctx = this.context[0];1425
1426return ctx._responsive ?1427$.inArray( false, ctx._responsive._responsiveOnlyHidden() ) !== -1 :1428false;1429} );1430
1431Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()', function () {1432return this.iterator( 'column', function ( settings, column ) {1433return settings._responsive ?1434settings._responsive._responsiveOnlyHidden()[ column ] :1435false;1436}, 1 );1437} );1438
1439
1440/**
1441* Version information
1442*
1443* @name Responsive.version
1444* @static
1445*/
1446Responsive.version = '2.2.9';1447
1448
1449$.fn.dataTable.Responsive = Responsive;1450$.fn.DataTable.Responsive = Responsive;1451
1452// Attach a listener to the document which listens for DataTables initialisation
1453// events so we can automatically initialise
1454$(document).on( 'preInit.dt.dtr', function (e, settings, json) {1455if ( e.namespace !== 'dt' ) {1456return;1457}1458
1459if ( $(settings.nTable).hasClass( 'responsive' ) ||1460$(settings.nTable).hasClass( 'dt-responsive' ) ||1461settings.oInit.responsive ||1462DataTable.defaults.responsive1463) {1464var init = settings.oInit.responsive;1465
1466if ( init !== false ) {1467new Responsive( settings, $.isPlainObject( init ) ? init : {} );1468}1469}1470} );1471
1472
1473return Responsive;1474}));1475