25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1475 lines
40KB

  1. /*! Responsive 2.2.9
  2. * 2014-2021 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary Responsive
  6. * @description Responsive tables plug-in for DataTables
  7. * @version 2.2.9
  8. * @file dataTables.responsive.js
  9. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  10. * @contact www.sprymedia.co.uk/contact
  11. * @copyright Copyright 2014-2021 SpryMedia Ltd.
  12. *
  13. * This source file is free software, available under the following license:
  14. * MIT license - http://datatables.net/license/mit
  15. *
  16. * This source file is distributed in the hope that it will be useful, but
  17. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  18. * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
  19. *
  20. * For details please refer to: http://www.datatables.net
  21. */
  22. (function( factory ){
  23. if ( typeof define === 'function' && define.amd ) {
  24. // AMD
  25. define( ['jquery', 'datatables.net'], function ( $ ) {
  26. return factory( $, window, document );
  27. } );
  28. }
  29. else if ( typeof exports === 'object' ) {
  30. // CommonJS
  31. module.exports = function (root, $) {
  32. if ( ! root ) {
  33. root = window;
  34. }
  35. if ( ! $ || ! $.fn.dataTable ) {
  36. $ = require('datatables.net')(root, $).$;
  37. }
  38. return factory( $, root, root.document );
  39. };
  40. }
  41. else {
  42. // Browser
  43. factory( jQuery, window, document );
  44. }
  45. }(function( $, window, document, undefined ) {
  46. 'use strict';
  47. var DataTable = $.fn.dataTable;
  48. /**
  49. * Responsive is a plug-in for the DataTables library that makes use of
  50. * DataTables' ability to change the visibility of columns, changing the
  51. * visibility of columns so the displayed columns fit into the table container.
  52. * The end result is that complex tables will be dynamically adjusted to fit
  53. * into the viewport, be it on a desktop, tablet or mobile browser.
  54. *
  55. * Responsive for DataTables has two modes of operation, which can used
  56. * individually or combined:
  57. *
  58. * * Class name based control - columns assigned class names that match the
  59. * breakpoint logic can be shown / hidden as required for each breakpoint.
  60. * * Automatic control - columns are automatically hidden when there is no
  61. * room left to display them. Columns removed from the right.
  62. *
  63. * In additional to column visibility control, Responsive also has built into
  64. * options to use DataTables' child row display to show / hide the information
  65. * from the table that has been hidden. There are also two modes of operation
  66. * for this child row display:
  67. *
  68. * * Inline - when the control element that the user can use to show / hide
  69. * child rows is displayed inside the first column of the table.
  70. * * Column - where a whole column is dedicated to be the show / hide control.
  71. *
  72. * Initialisation of Responsive is performed by:
  73. *
  74. * * Adding the class `responsive` or `dt-responsive` to the table. In this case
  75. * Responsive will automatically be initialised with the default configuration
  76. * options when the DataTable is created.
  77. * * Using the `responsive` option in the DataTables configuration options. This
  78. * can also be used to specify the configuration options, or simply set to
  79. * `true` to use the defaults.
  80. *
  81. * @class
  82. * @param {object} settings DataTables settings object for the host table
  83. * @param {object} [opts] Configuration options
  84. * @requires jQuery 1.7+
  85. * @requires DataTables 1.10.3+
  86. *
  87. * @example
  88. * $('#example').DataTable( {
  89. * responsive: true
  90. * } );
  91. * } );
  92. */
  93. var Responsive = function ( settings, opts ) {
  94. // Sanity check that we are using DataTables 1.10 or newer
  95. if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.10' ) ) {
  96. throw 'DataTables Responsive requires DataTables 1.10.10 or newer';
  97. }
  98. this.s = {
  99. dt: new DataTable.Api( settings ),
  100. columns: [],
  101. current: []
  102. };
  103. // Check if responsive has already been initialised on this table
  104. if ( this.s.dt.settings()[0].responsive ) {
  105. return;
  106. }
  107. // details is an object, but for simplicity the user can give it as a string
  108. // or a boolean
  109. if ( opts && typeof opts.details === 'string' ) {
  110. opts.details = { type: opts.details };
  111. }
  112. else if ( opts && opts.details === false ) {
  113. opts.details = { type: false };
  114. }
  115. else if ( opts && opts.details === true ) {
  116. opts.details = { type: 'inline' };
  117. }
  118. this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );
  119. settings.responsive = this;
  120. this._constructor();
  121. };
  122. $.extend( Responsive.prototype, {
  123. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  124. * Constructor
  125. */
  126. /**
  127. * Initialise the Responsive instance
  128. *
  129. * @private
  130. */
  131. _constructor: function ()
  132. {
  133. var that = this;
  134. var dt = this.s.dt;
  135. var dtPrivateSettings = dt.settings()[0];
  136. var oldWindowWidth = $(window).innerWidth();
  137. dt.settings()[0]._responsive = this;
  138. // Use DataTables' throttle function to avoid processor thrashing on
  139. // resize
  140. $(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {
  141. // iOS has a bug whereby resize can fire when only scrolling
  142. // See: http://stackoverflow.com/questions/8898412
  143. var width = $(window).innerWidth();
  144. if ( width !== oldWindowWidth ) {
  145. that._resize();
  146. oldWindowWidth = width;
  147. }
  148. } ) );
  149. // DataTables doesn't currently trigger an event when a row is added, so
  150. // we need to hook into its private API to enforce the hidden rows when
  151. // new data is added
  152. dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {
  153. if ( $.inArray( false, that.s.current ) !== -1 ) {
  154. $('>td, >th', tr).each( function ( i ) {
  155. var idx = dt.column.index( 'toData', i );
  156. if ( that.s.current[idx] === false ) {
  157. $(this).css('display', 'none');
  158. }
  159. } );
  160. }
  161. } );
  162. // Destroy event handler
  163. dt.on( 'destroy.dtr', function () {
  164. dt.off( '.dtr' );
  165. $( dt.table().body() ).off( '.dtr' );
  166. $(window).off( 'resize.dtr orientationchange.dtr' );
  167. dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control');
  168. // Restore the columns that we've hidden
  169. $.each( that.s.current, function ( i, val ) {
  170. if ( val === false ) {
  171. that._setColumnVis( i, true );
  172. }
  173. } );
  174. } );
  175. // Reorder the breakpoints array here in case they have been added out
  176. // of order
  177. this.c.breakpoints.sort( function (a, b) {
  178. return a.width < b.width ? 1 :
  179. a.width > b.width ? -1 : 0;
  180. } );
  181. this._classLogic();
  182. this._resizeAuto();
  183. // Details handler
  184. var details = this.c.details;
  185. if ( details.type !== false ) {
  186. that._detailsInit();
  187. // DataTables will trigger this event on every column it shows and
  188. // hides individually
  189. dt.on( 'column-visibility.dtr', function () {
  190. // Use a small debounce to allow multiple columns to be set together
  191. if ( that._timer ) {
  192. clearTimeout( that._timer );
  193. }
  194. that._timer = setTimeout( function () {
  195. that._timer = null;
  196. that._classLogic();
  197. that._resizeAuto();
  198. that._resize(true);
  199. that._redrawChildren();
  200. }, 100 );
  201. } );
  202. // Redraw the details box on each draw which will happen if the data
  203. // has changed. This is used until DataTables implements a native
  204. // `updated` event for rows
  205. dt.on( 'draw.dtr', function () {
  206. that._redrawChildren();
  207. } );
  208. $(dt.table().node()).addClass( 'dtr-'+details.type );
  209. }
  210. dt.on( 'column-reorder.dtr', function (e, settings, details) {
  211. that._classLogic();
  212. that._resizeAuto();
  213. that._resize(true);
  214. } );
  215. // Change in column sizes means we need to calc
  216. dt.on( 'column-sizing.dtr', function () {
  217. that._resizeAuto();
  218. that._resize();
  219. });
  220. // On Ajax reload we want to reopen any child rows which are displayed
  221. // by responsive
  222. dt.on( 'preXhr.dtr', function () {
  223. var rowIds = [];
  224. dt.rows().every( function () {
  225. if ( this.child.isShown() ) {
  226. rowIds.push( this.id(true) );
  227. }
  228. } );
  229. dt.one( 'draw.dtr', function () {
  230. that._resizeAuto();
  231. that._resize();
  232. dt.rows( rowIds ).every( function () {
  233. that._detailsDisplay( this, false );
  234. } );
  235. } );
  236. });
  237. dt
  238. .on( 'draw.dtr', function () {
  239. that._controlClass();
  240. })
  241. .on( 'init.dtr', function (e, settings, details) {
  242. if ( e.namespace !== 'dt' ) {
  243. return;
  244. }
  245. that._resizeAuto();
  246. that._resize();
  247. // If columns were hidden, then DataTables needs to adjust the
  248. // column sizing
  249. if ( $.inArray( false, that.s.current ) ) {
  250. dt.columns.adjust();
  251. }
  252. } );
  253. // First pass - draw the table for the current viewport size
  254. this._resize();
  255. },
  256. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  257. * Private methods
  258. */
  259. /**
  260. * Calculate the visibility for the columns in a table for a given
  261. * breakpoint. The result is pre-determined based on the class logic if
  262. * class names are used to control all columns, but the width of the table
  263. * is also used if there are columns which are to be automatically shown
  264. * and hidden.
  265. *
  266. * @param {string} breakpoint Breakpoint name to use for the calculation
  267. * @return {array} Array of boolean values initiating the visibility of each
  268. * column.
  269. * @private
  270. */
  271. _columnsVisiblity: function ( breakpoint )
  272. {
  273. var dt = this.s.dt;
  274. var columns = this.s.columns;
  275. var i, ien;
  276. // Create an array that defines the column ordering based first on the
  277. // column's priority, and secondly the column index. This allows the
  278. // columns to be removed from the right if the priority matches
  279. var order = columns
  280. .map( function ( col, idx ) {
  281. return {
  282. columnIdx: idx,
  283. priority: col.priority
  284. };
  285. } )
  286. .sort( function ( a, b ) {
  287. if ( a.priority !== b.priority ) {
  288. return a.priority - b.priority;
  289. }
  290. return a.columnIdx - b.columnIdx;
  291. } );
  292. // Class logic - determine which columns are in this breakpoint based
  293. // on the classes. If no class control (i.e. `auto`) then `-` is used
  294. // to indicate this to the rest of the function
  295. var display = $.map( columns, function ( col, i ) {
  296. if ( dt.column(i).visible() === false ) {
  297. return 'not-visible';
  298. }
  299. return col.auto && col.minWidth === null ?
  300. false :
  301. col.auto === true ?
  302. '-' :
  303. $.inArray( breakpoint, col.includeIn ) !== -1;
  304. } );
  305. // Auto column control - first pass: how much width is taken by the
  306. // ones that must be included from the non-auto columns
  307. var requiredWidth = 0;
  308. for ( i=0, ien=display.length ; i<ien ; i++ ) {
  309. if ( display[i] === true ) {
  310. requiredWidth += columns[i].minWidth;
  311. }
  312. }
  313. // Second pass, use up any remaining width for other columns. For
  314. // scrolling tables we need to subtract the width of the scrollbar. It
  315. // may not be requires which makes this sub-optimal, but it would
  316. // require another full redraw to make complete use of those extra few
  317. // pixels
  318. var scrolling = dt.settings()[0].oScroll;
  319. var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
  320. var widthAvailable = dt.table().container().offsetWidth - bar;
  321. var usedWidth = widthAvailable - requiredWidth;
  322. // Control column needs to always be included. This makes it sub-
  323. // optimal in terms of using the available with, but to stop layout
  324. // thrashing or overflow. Also we need to account for the control column
  325. // width first so we know how much width is available for the other
  326. // columns, since the control column might not be the first one shown
  327. for ( i=0, ien=display.length ; i<ien ; i++ ) {
  328. if ( columns[i].control ) {
  329. usedWidth -= columns[i].minWidth;
  330. }
  331. }
  332. // Allow columns to be shown (counting by priority and then right to
  333. // left) until we run out of room
  334. var empty = false;
  335. for ( i=0, ien=order.length ; i<ien ; i++ ) {
  336. var colIdx = order[i].columnIdx;
  337. if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
  338. // Once we've found a column that won't fit we don't let any
  339. // others display either, or columns might disappear in the
  340. // middle of the table
  341. if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
  342. empty = true;
  343. display[colIdx] = false;
  344. }
  345. else {
  346. display[colIdx] = true;
  347. }
  348. usedWidth -= columns[colIdx].minWidth;
  349. }
  350. }
  351. // Determine if the 'control' column should be shown (if there is one).
  352. // This is the case when there is a hidden column (that is not the
  353. // control column). The two loops look inefficient here, but they are
  354. // trivial and will fly through. We need to know the outcome from the
  355. // first , before the action in the second can be taken
  356. var showControl = false;
  357. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  358. if ( ! columns[i].control && ! columns[i].never && display[i] === false ) {
  359. showControl = true;
  360. break;
  361. }
  362. }
  363. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  364. if ( columns[i].control ) {
  365. display[i] = showControl;
  366. }
  367. // Replace not visible string with false from the control column detection above
  368. if ( display[i] === 'not-visible' ) {
  369. display[i] = false;
  370. }
  371. }
  372. // Finally we need to make sure that there is at least one column that
  373. // is visible
  374. if ( $.inArray( true, display ) === -1 ) {
  375. display[0] = true;
  376. }
  377. return display;
  378. },
  379. /**
  380. * Create the internal `columns` array with information about the columns
  381. * for the table. This includes determining which breakpoints the column
  382. * will appear in, based upon class names in the column, which makes up the
  383. * vast majority of this method.
  384. *
  385. * @private
  386. */
  387. _classLogic: function ()
  388. {
  389. var that = this;
  390. var calc = {};
  391. var breakpoints = this.c.breakpoints;
  392. var dt = this.s.dt;
  393. var columns = dt.columns().eq(0).map( function (i) {
  394. var column = this.column(i);
  395. var className = column.header().className;
  396. var priority = dt.settings()[0].aoColumns[i].responsivePriority;
  397. var dataPriority = column.header().getAttribute('data-priority');
  398. if ( priority === undefined ) {
  399. priority = dataPriority === undefined || dataPriority === null?
  400. 10000 :
  401. dataPriority * 1;
  402. }
  403. return {
  404. className: className,
  405. includeIn: [],
  406. auto: false,
  407. control: false,
  408. never: className.match(/\bnever\b/) ? true : false,
  409. priority: priority
  410. };
  411. } );
  412. // Simply add a breakpoint to `includeIn` array, ensuring that there are
  413. // no duplicates
  414. var add = function ( colIdx, name ) {
  415. var includeIn = columns[ colIdx ].includeIn;
  416. if ( $.inArray( name, includeIn ) === -1 ) {
  417. includeIn.push( name );
  418. }
  419. };
  420. var column = function ( colIdx, name, operator, matched ) {
  421. var size, i, ien;
  422. if ( ! operator ) {
  423. columns[ colIdx ].includeIn.push( name );
  424. }
  425. else if ( operator === 'max-' ) {
  426. // Add this breakpoint and all smaller
  427. size = that._find( name ).width;
  428. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  429. if ( breakpoints[i].width <= size ) {
  430. add( colIdx, breakpoints[i].name );
  431. }
  432. }
  433. }
  434. else if ( operator === 'min-' ) {
  435. // Add this breakpoint and all larger
  436. size = that._find( name ).width;
  437. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  438. if ( breakpoints[i].width >= size ) {
  439. add( colIdx, breakpoints[i].name );
  440. }
  441. }
  442. }
  443. else if ( operator === 'not-' ) {
  444. // Add all but this breakpoint
  445. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  446. if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
  447. add( colIdx, breakpoints[i].name );
  448. }
  449. }
  450. }
  451. };
  452. // Loop over each column and determine if it has a responsive control
  453. // class
  454. columns.each( function ( col, i ) {
  455. var classNames = col.className.split(' ');
  456. var hasClass = false;
  457. // Split the class name up so multiple rules can be applied if needed
  458. for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
  459. var className = classNames[k].trim();
  460. if ( className === 'all' ) {
  461. // Include in all
  462. hasClass = true;
  463. col.includeIn = $.map( breakpoints, function (a) {
  464. return a.name;
  465. } );
  466. return;
  467. }
  468. else if ( className === 'none' || col.never ) {
  469. // Include in none (default) and no auto
  470. hasClass = true;
  471. return;
  472. }
  473. else if ( className === 'control' || className === 'dtr-control' ) {
  474. // Special column that is only visible, when one of the other
  475. // columns is hidden. This is used for the details control
  476. hasClass = true;
  477. col.control = true;
  478. return;
  479. }
  480. $.each( breakpoints, function ( j, breakpoint ) {
  481. // Does this column have a class that matches this breakpoint?
  482. var brokenPoint = breakpoint.name.split('-');
  483. var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
  484. var match = className.match( re );
  485. if ( match ) {
  486. hasClass = true;
  487. if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
  488. // Class name matches breakpoint name fully
  489. column( i, breakpoint.name, match[1], match[2]+match[3] );
  490. }
  491. else if ( match[2] === brokenPoint[0] && ! match[3] ) {
  492. // Class name matched primary breakpoint name with no qualifier
  493. column( i, breakpoint.name, match[1], match[2] );
  494. }
  495. }
  496. } );
  497. }
  498. // If there was no control class, then automatic sizing is used
  499. if ( ! hasClass ) {
  500. col.auto = true;
  501. }
  502. } );
  503. this.s.columns = columns;
  504. },
  505. /**
  506. * Update the cells to show the correct control class / button
  507. * @private
  508. */
  509. _controlClass: function ()
  510. {
  511. if ( this.c.details.type === 'inline' ) {
  512. var dt = this.s.dt;
  513. var columnsVis = this.s.current;
  514. var firstVisible = $.inArray(true, columnsVis);
  515. // Remove from any cells which shouldn't have it
  516. dt.cells(
  517. null,
  518. function(idx) {
  519. return idx !== firstVisible;
  520. },
  521. {page: 'current'}
  522. )
  523. .nodes()
  524. .to$()
  525. .filter('.dtr-control')
  526. .removeClass('dtr-control');
  527. dt.cells(null, firstVisible, {page: 'current'})
  528. .nodes()
  529. .to$()
  530. .addClass('dtr-control');
  531. }
  532. },
  533. /**
  534. * Show the details for the child row
  535. *
  536. * @param {DataTables.Api} row API instance for the row
  537. * @param {boolean} update Update flag
  538. * @private
  539. */
  540. _detailsDisplay: function ( row, update )
  541. {
  542. var that = this;
  543. var dt = this.s.dt;
  544. var details = this.c.details;
  545. if ( details && details.type !== false ) {
  546. var res = details.display( row, update, function () {
  547. return details.renderer(
  548. dt, row[0], that._detailsObj(row[0])
  549. );
  550. } );
  551. if ( res === true || res === false ) {
  552. $(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
  553. }
  554. }
  555. },
  556. /**
  557. * Initialisation for the details handler
  558. *
  559. * @private
  560. */
  561. _detailsInit: function ()
  562. {
  563. var that = this;
  564. var dt = this.s.dt;
  565. var details = this.c.details;
  566. // The inline type always uses the first child as the target
  567. if ( details.type === 'inline' ) {
  568. details.target = 'td.dtr-control, th.dtr-control';
  569. }
  570. // Keyboard accessibility
  571. dt.on( 'draw.dtr', function () {
  572. that._tabIndexes();
  573. } );
  574. that._tabIndexes(); // Initial draw has already happened
  575. $( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
  576. if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
  577. $(this).click();
  578. }
  579. } );
  580. // type.target can be a string jQuery selector or a column index
  581. var target = details.target;
  582. var selector = typeof target === 'string' ? target : 'td, th';
  583. if ( target !== undefined || target !== null ) {
  584. // Click handler to show / hide the details rows when they are available
  585. $( dt.table().body() )
  586. .on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
  587. // If the table is not collapsed (i.e. there is no hidden columns)
  588. // then take no action
  589. if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
  590. return;
  591. }
  592. // Check that the row is actually a DataTable's controlled node
  593. if ( $.inArray( $(this).closest('tr').get(0), dt.rows().nodes().toArray() ) === -1 ) {
  594. return;
  595. }
  596. // For column index, we determine if we should act or not in the
  597. // handler - otherwise it is already okay
  598. if ( typeof target === 'number' ) {
  599. var targetIdx = target < 0 ?
  600. dt.columns().eq(0).length + target :
  601. target;
  602. if ( dt.cell( this ).index().column !== targetIdx ) {
  603. return;
  604. }
  605. }
  606. // $().closest() includes itself in its check
  607. var row = dt.row( $(this).closest('tr') );
  608. // Check event type to do an action
  609. if ( e.type === 'click' ) {
  610. // The renderer is given as a function so the caller can execute it
  611. // only when they need (i.e. if hiding there is no point is running
  612. // the renderer)
  613. that._detailsDisplay( row, false );
  614. }
  615. else if ( e.type === 'mousedown' ) {
  616. // For mouse users, prevent the focus ring from showing
  617. $(this).css('outline', 'none');
  618. }
  619. else if ( e.type === 'mouseup' ) {
  620. // And then re-allow at the end of the click
  621. $(this).trigger('blur').css('outline', '');
  622. }
  623. } );
  624. }
  625. },
  626. /**
  627. * Get the details to pass to a renderer for a row
  628. * @param {int} rowIdx Row index
  629. * @private
  630. */
  631. _detailsObj: function ( rowIdx )
  632. {
  633. var that = this;
  634. var dt = this.s.dt;
  635. return $.map( this.s.columns, function( col, i ) {
  636. // Never and control columns should not be passed to the renderer
  637. if ( col.never || col.control ) {
  638. return;
  639. }
  640. var dtCol = dt.settings()[0].aoColumns[ i ];
  641. return {
  642. className: dtCol.sClass,
  643. columnIndex: i,
  644. data: dt.cell( rowIdx, i ).render( that.c.orthogonal ),
  645. hidden: dt.column( i ).visible() && !that.s.current[ i ],
  646. rowIndex: rowIdx,
  647. title: dtCol.sTitle !== null ?
  648. dtCol.sTitle :
  649. $(dt.column(i).header()).text()
  650. };
  651. } );
  652. },
  653. /**
  654. * Find a breakpoint object from a name
  655. *
  656. * @param {string} name Breakpoint name to find
  657. * @return {object} Breakpoint description object
  658. * @private
  659. */
  660. _find: function ( name )
  661. {
  662. var breakpoints = this.c.breakpoints;
  663. for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  664. if ( breakpoints[i].name === name ) {
  665. return breakpoints[i];
  666. }
  667. }
  668. },
  669. /**
  670. * Re-create the contents of the child rows as the display has changed in
  671. * some way.
  672. *
  673. * @private
  674. */
  675. _redrawChildren: function ()
  676. {
  677. var that = this;
  678. var dt = this.s.dt;
  679. dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
  680. var row = dt.row( idx );
  681. that._detailsDisplay( dt.row( idx ), true );
  682. } );
  683. },
  684. /**
  685. * Alter the table display for a resized viewport. This involves first
  686. * determining what breakpoint the window currently is in, getting the
  687. * column visibilities to apply and then setting them.
  688. *
  689. * @param {boolean} forceRedraw Force a redraw
  690. * @private
  691. */
  692. _resize: function (forceRedraw)
  693. {
  694. var that = this;
  695. var dt = this.s.dt;
  696. var width = $(window).innerWidth();
  697. var breakpoints = this.c.breakpoints;
  698. var breakpoint = breakpoints[0].name;
  699. var columns = this.s.columns;
  700. var i, ien;
  701. var oldVis = this.s.current.slice();
  702. // Determine what breakpoint we are currently at
  703. for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
  704. if ( width <= breakpoints[i].width ) {
  705. breakpoint = breakpoints[i].name;
  706. break;
  707. }
  708. }
  709. // Show the columns for that break point
  710. var columnsVis = this._columnsVisiblity( breakpoint );
  711. this.s.current = columnsVis;
  712. // Set the class before the column visibility is changed so event
  713. // listeners know what the state is. Need to determine if there are
  714. // any columns that are not visible but can be shown
  715. var collapsedClass = false;
  716. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  717. if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control && ! dt.column(i).visible() === false ) {
  718. collapsedClass = true;
  719. break;
  720. }
  721. }
  722. $( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );
  723. var changed = false;
  724. var visible = 0;
  725. dt.columns().eq(0).each( function ( colIdx, i ) {
  726. if ( columnsVis[i] === true ) {
  727. visible++;
  728. }
  729. if ( forceRedraw || columnsVis[i] !== oldVis[i] ) {
  730. changed = true;
  731. that._setColumnVis( colIdx, columnsVis[i] );
  732. }
  733. } );
  734. if ( changed ) {
  735. this._redrawChildren();
  736. // Inform listeners of the change
  737. $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
  738. // If no records, update the "No records" display element
  739. if ( dt.page.info().recordsDisplay === 0 ) {
  740. $('td', dt.table().body()).eq(0).attr('colspan', visible);
  741. }
  742. }
  743. that._controlClass();
  744. },
  745. /**
  746. * Determine the width of each column in the table so the auto column hiding
  747. * has that information to work with. This method is never going to be 100%
  748. * perfect since column widths can change slightly per page, but without
  749. * seriously compromising performance this is quite effective.
  750. *
  751. * @private
  752. */
  753. _resizeAuto: function ()
  754. {
  755. var dt = this.s.dt;
  756. var columns = this.s.columns;
  757. // Are we allowed to do auto sizing?
  758. if ( ! this.c.auto ) {
  759. return;
  760. }
  761. // Are there any columns that actually need auto-sizing, or do they all
  762. // have classes defined
  763. if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
  764. return;
  765. }
  766. // Need to restore all children. They will be reinstated by a re-render
  767. if ( ! $.isEmptyObject( _childNodeStore ) ) {
  768. $.each( _childNodeStore, function ( key ) {
  769. var idx = key.split('-');
  770. _childNodesRestore( dt, idx[0]*1, idx[1]*1 );
  771. } );
  772. }
  773. // Clone the table with the current data in it
  774. var tableWidth = dt.table().node().offsetWidth;
  775. var columnWidths = dt.columns;
  776. var clonedTable = dt.table().node().cloneNode( false );
  777. var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
  778. var clonedBody = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8
  779. clonedTable.style.width = 'auto';
  780. // Header
  781. var headerCells = dt.columns()
  782. .header()
  783. .filter( function (idx) {
  784. return dt.column(idx).visible();
  785. } )
  786. .to$()
  787. .clone( false )
  788. .css( 'display', 'table-cell' )
  789. .css( 'width', 'auto' )
  790. .css( 'min-width', 0 );
  791. // Body rows - we don't need to take account of DataTables' column
  792. // visibility since we implement our own here (hence the `display` set)
  793. $(clonedBody)
  794. .append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
  795. .find( 'th, td' ).css( 'display', '' );
  796. // Footer
  797. var footer = dt.table().footer();
  798. if ( footer ) {
  799. var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
  800. var footerCells = dt.columns()
  801. .footer()
  802. .filter( function (idx) {
  803. return dt.column(idx).visible();
  804. } )
  805. .to$()
  806. .clone( false )
  807. .css( 'display', 'table-cell' );
  808. $('<tr/>')
  809. .append( footerCells )
  810. .appendTo( clonedFooter );
  811. }
  812. $('<tr/>')
  813. .append( headerCells )
  814. .appendTo( clonedHeader );
  815. // In the inline case extra padding is applied to the first column to
  816. // give space for the show / hide icon. We need to use this in the
  817. // calculation
  818. if ( this.c.details.type === 'inline' ) {
  819. $(clonedTable).addClass( 'dtr-inline collapsed' );
  820. }
  821. // It is unsafe to insert elements with the same name into the DOM
  822. // multiple times. For example, cloning and inserting a checked radio
  823. // clears the chcecked state of the original radio.
  824. $( clonedTable ).find( '[name]' ).removeAttr( 'name' );
  825. // A position absolute table would take the table out of the flow of
  826. // our container element, bypassing the height and width (Scroller)
  827. $( clonedTable ).css( 'position', 'relative' )
  828. var inserted = $('<div/>')
  829. .css( {
  830. width: 1,
  831. height: 1,
  832. overflow: 'hidden',
  833. clear: 'both'
  834. } )
  835. .append( clonedTable );
  836. inserted.insertBefore( dt.table().node() );
  837. // The cloned header now contains the smallest that each column can be
  838. headerCells.each( function (i) {
  839. var idx = dt.column.index( 'fromVisible', i );
  840. columns[ idx ].minWidth = this.offsetWidth || 0;
  841. } );
  842. inserted.remove();
  843. },
  844. /**
  845. * Get the state of the current hidden columns - controlled by Responsive only
  846. */
  847. _responsiveOnlyHidden: function ()
  848. {
  849. var dt = this.s.dt;
  850. return $.map( this.s.current, function (v, i) {
  851. // If the column is hidden by DataTables then it can't be hidden by
  852. // Responsive!
  853. if ( dt.column(i).visible() === false ) {
  854. return true;
  855. }
  856. return v;
  857. } );
  858. },
  859. /**
  860. * Set a column's visibility.
  861. *
  862. * We don't use DataTables' column visibility controls in order to ensure
  863. * that column visibility can Responsive can no-exist. Since only IE8+ is
  864. * supported (and all evergreen browsers of course) the control of the
  865. * display attribute works well.
  866. *
  867. * @param {integer} col Column index
  868. * @param {boolean} showHide Show or hide (true or false)
  869. * @private
  870. */
  871. _setColumnVis: function ( col, showHide )
  872. {
  873. var dt = this.s.dt;
  874. var display = showHide ? '' : 'none'; // empty string will remove the attr
  875. $( dt.column( col ).header() ).css( 'display', display );
  876. $( dt.column( col ).footer() ).css( 'display', display );
  877. dt.column( col ).nodes().to$().css( 'display', display );
  878. // If the are child nodes stored, we might need to reinsert them
  879. if ( ! $.isEmptyObject( _childNodeStore ) ) {
  880. dt.cells( null, col ).indexes().each( function (idx) {
  881. _childNodesRestore( dt, idx.row, idx.column );
  882. } );
  883. }
  884. },
  885. /**
  886. * Update the cell tab indexes for keyboard accessibility. This is called on
  887. * every table draw - that is potentially inefficient, but also the least
  888. * complex option given that column visibility can change on the fly. Its a
  889. * shame user-focus was removed from CSS 3 UI, as it would have solved this
  890. * issue with a single CSS statement.
  891. *
  892. * @private
  893. */
  894. _tabIndexes: function ()
  895. {
  896. var dt = this.s.dt;
  897. var cells = dt.cells( { page: 'current' } ).nodes().to$();
  898. var ctx = dt.settings()[0];
  899. var target = this.c.details.target;
  900. cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );
  901. if ( typeof target === 'number' ) {
  902. dt.cells( null, target, { page: 'current' } ).nodes().to$()
  903. .attr( 'tabIndex', ctx.iTabIndex )
  904. .data( 'dtr-keyboard', 1 );
  905. }
  906. else {
  907. // This is a bit of a hack - we need to limit the selected nodes to just
  908. // those of this table
  909. if ( target === 'td:first-child, th:first-child' ) {
  910. target = '>td:first-child, >th:first-child';
  911. }
  912. $( target, dt.rows( { page: 'current' } ).nodes() )
  913. .attr( 'tabIndex', ctx.iTabIndex )
  914. .data( 'dtr-keyboard', 1 );
  915. }
  916. }
  917. } );
  918. /**
  919. * List of default breakpoints. Each item in the array is an object with two
  920. * properties:
  921. *
  922. * * `name` - the breakpoint name.
  923. * * `width` - the breakpoint width
  924. *
  925. * @name Responsive.breakpoints
  926. * @static
  927. */
  928. Responsive.breakpoints = [
  929. { name: 'desktop', width: Infinity },
  930. { name: 'tablet-l', width: 1024 },
  931. { name: 'tablet-p', width: 768 },
  932. { name: 'mobile-l', width: 480 },
  933. { name: 'mobile-p', width: 320 }
  934. ];
  935. /**
  936. * Display methods - functions which define how the hidden data should be shown
  937. * in the table.
  938. *
  939. * @namespace
  940. * @name Responsive.defaults
  941. * @static
  942. */
  943. Responsive.display = {
  944. childRow: function ( row, update, render ) {
  945. if ( update ) {
  946. if ( $(row.node()).hasClass('parent') ) {
  947. row.child( render(), 'child' ).show();
  948. return true;
  949. }
  950. }
  951. else {
  952. if ( ! row.child.isShown() ) {
  953. row.child( render(), 'child' ).show();
  954. $( row.node() ).addClass( 'parent' );
  955. return true;
  956. }
  957. else {
  958. row.child( false );
  959. $( row.node() ).removeClass( 'parent' );
  960. return false;
  961. }
  962. }
  963. },
  964. childRowImmediate: function ( row, update, render ) {
  965. if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
  966. // User interaction and the row is show, or nothing to show
  967. row.child( false );
  968. $( row.node() ).removeClass( 'parent' );
  969. return false;
  970. }
  971. else {
  972. // Display
  973. row.child( render(), 'child' ).show();
  974. $( row.node() ).addClass( 'parent' );
  975. return true;
  976. }
  977. },
  978. // This is a wrapper so the modal options for Bootstrap and jQuery UI can
  979. // have options passed into them. This specific one doesn't need to be a
  980. // function but it is for consistency in the `modal` name
  981. modal: function ( options ) {
  982. return function ( row, update, render ) {
  983. if ( ! update ) {
  984. // Show a modal
  985. var close = function () {
  986. modal.remove(); // will tidy events for us
  987. $(document).off( 'keypress.dtr' );
  988. };
  989. var modal = $('<div class="dtr-modal"/>')
  990. .append( $('<div class="dtr-modal-display"/>')
  991. .append( $('<div class="dtr-modal-content"/>')
  992. .append( render() )
  993. )
  994. .append( $('<div class="dtr-modal-close">&times;</div>' )
  995. .click( function () {
  996. close();
  997. } )
  998. )
  999. )
  1000. .append( $('<div class="dtr-modal-background"/>')
  1001. .click( function () {
  1002. close();
  1003. } )
  1004. )
  1005. .appendTo( 'body' );
  1006. $(document).on( 'keyup.dtr', function (e) {
  1007. if ( e.keyCode === 27 ) {
  1008. e.stopPropagation();
  1009. close();
  1010. }
  1011. } );
  1012. }
  1013. else {
  1014. $('div.dtr-modal-content')
  1015. .empty()
  1016. .append( render() );
  1017. }
  1018. if ( options && options.header ) {
  1019. $('div.dtr-modal-content').prepend(
  1020. '<h2>'+options.header( row )+'</h2>'
  1021. );
  1022. }
  1023. };
  1024. }
  1025. };
  1026. var _childNodeStore = {};
  1027. function _childNodes( dt, row, col ) {
  1028. var name = row+'-'+col;
  1029. if ( _childNodeStore[ name ] ) {
  1030. return _childNodeStore[ name ];
  1031. }
  1032. // https://jsperf.com/childnodes-array-slice-vs-loop
  1033. var nodes = [];
  1034. var children = dt.cell( row, col ).node().childNodes;
  1035. for ( var i=0, ien=children.length ; i<ien ; i++ ) {
  1036. nodes.push( children[i] );
  1037. }
  1038. _childNodeStore[ name ] = nodes;
  1039. return nodes;
  1040. }
  1041. function _childNodesRestore( dt, row, col ) {
  1042. var name = row+'-'+col;
  1043. if ( ! _childNodeStore[ name ] ) {
  1044. return;
  1045. }
  1046. var node = dt.cell( row, col ).node();
  1047. var store = _childNodeStore[ name ];
  1048. var parent = store[0].parentNode;
  1049. var parentChildren = parent.childNodes;
  1050. var a = [];
  1051. for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
  1052. a.push( parentChildren[i] );
  1053. }
  1054. for ( var j=0, jen=a.length ; j<jen ; j++ ) {
  1055. node.appendChild( a[j] );
  1056. }
  1057. _childNodeStore[ name ] = undefined;
  1058. }
  1059. /**
  1060. * Display methods - functions which define how the hidden data should be shown
  1061. * in the table.
  1062. *
  1063. * @namespace
  1064. * @name Responsive.defaults
  1065. * @static
  1066. */
  1067. Responsive.renderer = {
  1068. listHiddenNodes: function () {
  1069. return function ( api, rowIdx, columns ) {
  1070. var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>');
  1071. var found = false;
  1072. var data = $.each( columns, function ( i, col ) {
  1073. if ( col.hidden ) {
  1074. var klass = col.className ?
  1075. 'class="'+ col.className +'"' :
  1076. '';
  1077. $(
  1078. '<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
  1079. '<span class="dtr-title">'+
  1080. col.title+
  1081. '</span> '+
  1082. '</li>'
  1083. )
  1084. .append( $('<span class="dtr-data"/>').append( _childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
  1085. .appendTo( ul );
  1086. found = true;
  1087. }
  1088. } );
  1089. return found ?
  1090. ul :
  1091. false;
  1092. };
  1093. },
  1094. listHidden: function () {
  1095. return function ( api, rowIdx, columns ) {
  1096. var data = $.map( columns, function ( col ) {
  1097. var klass = col.className ?
  1098. 'class="'+ col.className +'"' :
  1099. '';
  1100. return col.hidden ?
  1101. '<li '+klass+' data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
  1102. '<span class="dtr-title">'+
  1103. col.title+
  1104. '</span> '+
  1105. '<span class="dtr-data">'+
  1106. col.data+
  1107. '</span>'+
  1108. '</li>' :
  1109. '';
  1110. } ).join('');
  1111. return data ?
  1112. $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>').append( data ) :
  1113. false;
  1114. }
  1115. },
  1116. tableAll: function ( options ) {
  1117. options = $.extend( {
  1118. tableClass: ''
  1119. }, options );
  1120. return function ( api, rowIdx, columns ) {
  1121. var data = $.map( columns, function ( col ) {
  1122. var klass = col.className ?
  1123. 'class="'+ col.className +'"' :
  1124. '';
  1125. return '<tr '+klass+' data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
  1126. '<td>'+col.title+':'+'</td> '+
  1127. '<td>'+col.data+'</td>'+
  1128. '</tr>';
  1129. } ).join('');
  1130. return $('<table class="'+options.tableClass+' dtr-details" width="100%"/>').append( data );
  1131. }
  1132. }
  1133. };
  1134. /**
  1135. * Responsive default settings for initialisation
  1136. *
  1137. * @namespace
  1138. * @name Responsive.defaults
  1139. * @static
  1140. */
  1141. Responsive.defaults = {
  1142. /**
  1143. * List of breakpoints for the instance. Note that this means that each
  1144. * instance can have its own breakpoints. Additionally, the breakpoints
  1145. * cannot be changed once an instance has been creased.
  1146. *
  1147. * @type {Array}
  1148. * @default Takes the value of `Responsive.breakpoints`
  1149. */
  1150. breakpoints: Responsive.breakpoints,
  1151. /**
  1152. * Enable / disable auto hiding calculations. It can help to increase
  1153. * performance slightly if you disable this option, but all columns would
  1154. * need to have breakpoint classes assigned to them
  1155. *
  1156. * @type {Boolean}
  1157. * @default `true`
  1158. */
  1159. auto: true,
  1160. /**
  1161. * Details control. If given as a string value, the `type` property of the
  1162. * default object is set to that value, and the defaults used for the rest
  1163. * of the object - this is for ease of implementation.
  1164. *
  1165. * The object consists of the following properties:
  1166. *
  1167. * * `display` - A function that is used to show and hide the hidden details
  1168. * * `renderer` - function that is called for display of the child row data.
  1169. * The default function will show the data from the hidden columns
  1170. * * `target` - Used as the selector for what objects to attach the child
  1171. * open / close to
  1172. * * `type` - `false` to disable the details display, `inline` or `column`
  1173. * for the two control types
  1174. *
  1175. * @type {Object|string}
  1176. */
  1177. details: {
  1178. display: Responsive.display.childRow,
  1179. renderer: Responsive.renderer.listHidden(),
  1180. target: 0,
  1181. type: 'inline'
  1182. },
  1183. /**
  1184. * Orthogonal data request option. This is used to define the data type
  1185. * requested when Responsive gets the data to show in the child row.
  1186. *
  1187. * @type {String}
  1188. */
  1189. orthogonal: 'display'
  1190. };
  1191. /*
  1192. * API
  1193. */
  1194. var Api = $.fn.dataTable.Api;
  1195. // Doesn't do anything - work around for a bug in DT... Not documented
  1196. Api.register( 'responsive()', function () {
  1197. return this;
  1198. } );
  1199. Api.register( 'responsive.index()', function ( li ) {
  1200. li = $(li);
  1201. return {
  1202. column: li.data('dtr-index'),
  1203. row: li.parent().data('dtr-index')
  1204. };
  1205. } );
  1206. Api.register( 'responsive.rebuild()', function () {
  1207. return this.iterator( 'table', function ( ctx ) {
  1208. if ( ctx._responsive ) {
  1209. ctx._responsive._classLogic();
  1210. }
  1211. } );
  1212. } );
  1213. Api.register( 'responsive.recalc()', function () {
  1214. return this.iterator( 'table', function ( ctx ) {
  1215. if ( ctx._responsive ) {
  1216. ctx._responsive._resizeAuto();
  1217. ctx._responsive._resize();
  1218. }
  1219. } );
  1220. } );
  1221. Api.register( 'responsive.hasHidden()', function () {
  1222. var ctx = this.context[0];
  1223. return ctx._responsive ?
  1224. $.inArray( false, ctx._responsive._responsiveOnlyHidden() ) !== -1 :
  1225. false;
  1226. } );
  1227. Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()', function () {
  1228. return this.iterator( 'column', function ( settings, column ) {
  1229. return settings._responsive ?
  1230. settings._responsive._responsiveOnlyHidden()[ column ] :
  1231. false;
  1232. }, 1 );
  1233. } );
  1234. /**
  1235. * Version information
  1236. *
  1237. * @name Responsive.version
  1238. * @static
  1239. */
  1240. Responsive.version = '2.2.9';
  1241. $.fn.dataTable.Responsive = Responsive;
  1242. $.fn.DataTable.Responsive = Responsive;
  1243. // Attach a listener to the document which listens for DataTables initialisation
  1244. // events so we can automatically initialise
  1245. $(document).on( 'preInit.dt.dtr', function (e, settings, json) {
  1246. if ( e.namespace !== 'dt' ) {
  1247. return;
  1248. }
  1249. if ( $(settings.nTable).hasClass( 'responsive' ) ||
  1250. $(settings.nTable).hasClass( 'dt-responsive' ) ||
  1251. settings.oInit.responsive ||
  1252. DataTable.defaults.responsive
  1253. ) {
  1254. var init = settings.oInit.responsive;
  1255. if ( init !== false ) {
  1256. new Responsive( settings, $.isPlainObject( init ) ? init : {} );
  1257. }
  1258. }
  1259. } );
  1260. return Responsive;
  1261. }));