dataTables.responsive.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. /*! Responsive 2.0.2
  2. * 2014-2016 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary Responsive
  6. * @description Responsive tables plug-in for DataTables
  7. * @version 2.0.2
  8. * @file dataTables.responsive.js
  9. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  10. * @contact www.sprymedia.co.uk/contact
  11. * @copyright Copyright 2014-2016 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.3' ) ) {
  96. throw 'DataTables Responsive requires DataTables 1.10.3 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).width();
  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).width();
  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. // Restore the columns that we've hidden
  168. $.each( that.s.current, function ( i, val ) {
  169. if ( val === false ) {
  170. that._setColumnVis( i, true );
  171. }
  172. } );
  173. } );
  174. // Reorder the breakpoints array here in case they have been added out
  175. // of order
  176. this.c.breakpoints.sort( function (a, b) {
  177. return a.width < b.width ? 1 :
  178. a.width > b.width ? -1 : 0;
  179. } );
  180. this._classLogic();
  181. this._resizeAuto();
  182. // Details handler
  183. var details = this.c.details;
  184. if ( details.type !== false ) {
  185. that._detailsInit();
  186. // DataTables will trigger this event on every column it shows and
  187. // hides individually
  188. dt.on( 'column-visibility.dtr', function (e, ctx, col, vis) {
  189. that._classLogic();
  190. that._resizeAuto();
  191. that._resize();
  192. } );
  193. // Redraw the details box on each draw which will happen if the data
  194. // has changed. This is used until DataTables implements a native
  195. // `updated` event for rows
  196. dt.on( 'draw.dtr', function () {
  197. that._redrawChildren();
  198. } );
  199. $(dt.table().node()).addClass( 'dtr-'+details.type );
  200. }
  201. dt.on( 'column-reorder.dtr', function (e, settings, details) {
  202. that._classLogic();
  203. that._resizeAuto();
  204. that._resize();
  205. } );
  206. // Change in column sizes means we need to calc
  207. dt.on( 'column-sizing.dtr', function () {
  208. that._resize();
  209. });
  210. dt.on( 'init.dtr', function (e, settings, details) {
  211. that._resizeAuto();
  212. that._resize();
  213. // If columns were hidden, then DataTables needs to adjust the
  214. // column sizing
  215. if ( $.inArray( false, that.s.current ) ) {
  216. dt.columns.adjust();
  217. }
  218. } );
  219. // First pass - draw the table for the current viewport size
  220. this._resize();
  221. },
  222. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  223. * Private methods
  224. */
  225. /**
  226. * Calculate the visibility for the columns in a table for a given
  227. * breakpoint. The result is pre-determined based on the class logic if
  228. * class names are used to control all columns, but the width of the table
  229. * is also used if there are columns which are to be automatically shown
  230. * and hidden.
  231. *
  232. * @param {string} breakpoint Breakpoint name to use for the calculation
  233. * @return {array} Array of boolean values initiating the visibility of each
  234. * column.
  235. * @private
  236. */
  237. _columnsVisiblity: function ( breakpoint )
  238. {
  239. var dt = this.s.dt;
  240. var columns = this.s.columns;
  241. var i, ien;
  242. // Create an array that defines the column ordering based first on the
  243. // column's priority, and secondly the column index. This allows the
  244. // columns to be removed from the right if the priority matches
  245. var order = columns
  246. .map( function ( col, idx ) {
  247. return {
  248. columnIdx: idx,
  249. priority: col.priority
  250. };
  251. } )
  252. .sort( function ( a, b ) {
  253. if ( a.priority !== b.priority ) {
  254. return a.priority - b.priority;
  255. }
  256. return a.columnIdx - b.columnIdx;
  257. } );
  258. // Class logic - determine which columns are in this breakpoint based
  259. // on the classes. If no class control (i.e. `auto`) then `-` is used
  260. // to indicate this to the rest of the function
  261. var display = $.map( columns, function ( col ) {
  262. return col.auto && col.minWidth === null ?
  263. false :
  264. col.auto === true ?
  265. '-' :
  266. $.inArray( breakpoint, col.includeIn ) !== -1;
  267. } );
  268. // Auto column control - first pass: how much width is taken by the
  269. // ones that must be included from the non-auto columns
  270. var requiredWidth = 0;
  271. for ( i=0, ien=display.length ; i<ien ; i++ ) {
  272. if ( display[i] === true ) {
  273. requiredWidth += columns[i].minWidth;
  274. }
  275. }
  276. // Second pass, use up any remaining width for other columns. For
  277. // scrolling tables we need to subtract the width of the scrollbar. It
  278. // may not be requires which makes this sub-optimal, but it would
  279. // require another full redraw to make complete use of those extra few
  280. // pixels
  281. var scrolling = dt.settings()[0].oScroll;
  282. var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
  283. var widthAvailable = dt.table().container().offsetWidth - bar;
  284. var usedWidth = widthAvailable - requiredWidth;
  285. // Control column needs to always be included. This makes it sub-
  286. // optimal in terms of using the available with, but to stop layout
  287. // thrashing or overflow. Also we need to account for the control column
  288. // width first so we know how much width is available for the other
  289. // columns, since the control column might not be the first one shown
  290. for ( i=0, ien=display.length ; i<ien ; i++ ) {
  291. if ( columns[i].control ) {
  292. usedWidth -= columns[i].minWidth;
  293. }
  294. }
  295. // Allow columns to be shown (counting by priority and then right to
  296. // left) until we run out of room
  297. var empty = false;
  298. for ( i=0, ien=order.length ; i<ien ; i++ ) {
  299. var colIdx = order[i].columnIdx;
  300. if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
  301. // Once we've found a column that won't fit we don't let any
  302. // others display either, or columns might disappear in the
  303. // middle of the table
  304. if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
  305. empty = true;
  306. display[colIdx] = false;
  307. }
  308. else {
  309. display[colIdx] = true;
  310. }
  311. usedWidth -= columns[colIdx].minWidth;
  312. }
  313. }
  314. // Determine if the 'control' column should be shown (if there is one).
  315. // This is the case when there is a hidden column (that is not the
  316. // control column). The two loops look inefficient here, but they are
  317. // trivial and will fly through. We need to know the outcome from the
  318. // first , before the action in the second can be taken
  319. var showControl = false;
  320. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  321. if ( ! columns[i].control && ! columns[i].never && ! display[i] ) {
  322. showControl = true;
  323. break;
  324. }
  325. }
  326. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  327. if ( columns[i].control ) {
  328. display[i] = showControl;
  329. }
  330. }
  331. // Finally we need to make sure that there is at least one column that
  332. // is visible
  333. if ( $.inArray( true, display ) === -1 ) {
  334. display[0] = true;
  335. }
  336. return display;
  337. },
  338. /**
  339. * Create the internal `columns` array with information about the columns
  340. * for the table. This includes determining which breakpoints the column
  341. * will appear in, based upon class names in the column, which makes up the
  342. * vast majority of this method.
  343. *
  344. * @private
  345. */
  346. _classLogic: function ()
  347. {
  348. var that = this;
  349. var calc = {};
  350. var breakpoints = this.c.breakpoints;
  351. var dt = this.s.dt;
  352. var columns = dt.columns().eq(0).map( function (i) {
  353. var column = this.column(i);
  354. var className = column.header().className;
  355. var priority = dt.settings()[0].aoColumns[i].responsivePriority;
  356. if ( priority === undefined ) {
  357. var dataPriority = $(column.header()).data('priority');
  358. priority = dataPriority !== undefined ?
  359. dataPriority * 1 :
  360. 10000;
  361. }
  362. return {
  363. className: className,
  364. includeIn: [],
  365. auto: false,
  366. control: false,
  367. never: className.match(/\bnever\b/) ? true : false,
  368. priority: priority
  369. };
  370. } );
  371. // Simply add a breakpoint to `includeIn` array, ensuring that there are
  372. // no duplicates
  373. var add = function ( colIdx, name ) {
  374. var includeIn = columns[ colIdx ].includeIn;
  375. if ( $.inArray( name, includeIn ) === -1 ) {
  376. includeIn.push( name );
  377. }
  378. };
  379. var column = function ( colIdx, name, operator, matched ) {
  380. var size, i, ien;
  381. if ( ! operator ) {
  382. columns[ colIdx ].includeIn.push( name );
  383. }
  384. else if ( operator === 'max-' ) {
  385. // Add this breakpoint and all smaller
  386. size = that._find( name ).width;
  387. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  388. if ( breakpoints[i].width <= size ) {
  389. add( colIdx, breakpoints[i].name );
  390. }
  391. }
  392. }
  393. else if ( operator === 'min-' ) {
  394. // Add this breakpoint and all larger
  395. size = that._find( name ).width;
  396. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  397. if ( breakpoints[i].width >= size ) {
  398. add( colIdx, breakpoints[i].name );
  399. }
  400. }
  401. }
  402. else if ( operator === 'not-' ) {
  403. // Add all but this breakpoint
  404. for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  405. if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
  406. add( colIdx, breakpoints[i].name );
  407. }
  408. }
  409. }
  410. };
  411. // Loop over each column and determine if it has a responsive control
  412. // class
  413. columns.each( function ( col, i ) {
  414. var classNames = col.className.split(' ');
  415. var hasClass = false;
  416. // Split the class name up so multiple rules can be applied if needed
  417. for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
  418. var className = $.trim( classNames[k] );
  419. if ( className === 'all' ) {
  420. // Include in all
  421. hasClass = true;
  422. col.includeIn = $.map( breakpoints, function (a) {
  423. return a.name;
  424. } );
  425. return;
  426. }
  427. else if ( className === 'none' || col.never ) {
  428. // Include in none (default) and no auto
  429. hasClass = true;
  430. return;
  431. }
  432. else if ( className === 'control' ) {
  433. // Special column that is only visible, when one of the other
  434. // columns is hidden. This is used for the details control
  435. hasClass = true;
  436. col.control = true;
  437. return;
  438. }
  439. $.each( breakpoints, function ( j, breakpoint ) {
  440. // Does this column have a class that matches this breakpoint?
  441. var brokenPoint = breakpoint.name.split('-');
  442. var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
  443. var match = className.match( re );
  444. if ( match ) {
  445. hasClass = true;
  446. if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
  447. // Class name matches breakpoint name fully
  448. column( i, breakpoint.name, match[1], match[2]+match[3] );
  449. }
  450. else if ( match[2] === brokenPoint[0] && ! match[3] ) {
  451. // Class name matched primary breakpoint name with no qualifier
  452. column( i, breakpoint.name, match[1], match[2] );
  453. }
  454. }
  455. } );
  456. }
  457. // If there was no control class, then automatic sizing is used
  458. if ( ! hasClass ) {
  459. col.auto = true;
  460. }
  461. } );
  462. this.s.columns = columns;
  463. },
  464. /**
  465. * Show the details for the child row
  466. *
  467. * @param {DataTables.Api} row API instance for the row
  468. * @param {boolean} update Update flag
  469. * @private
  470. */
  471. _detailsDisplay: function ( row, update )
  472. {
  473. var that = this;
  474. var dt = this.s.dt;
  475. var details = this.c.details;
  476. if ( details && details.type ) {
  477. var res = details.display( row, update, function () {
  478. return details.renderer(
  479. dt, row[0], that._detailsObj(row[0])
  480. );
  481. } );
  482. if ( res === true || res === false ) {
  483. $(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
  484. }
  485. }
  486. },
  487. /**
  488. * Initialisation for the details handler
  489. *
  490. * @private
  491. */
  492. _detailsInit: function ()
  493. {
  494. var that = this;
  495. var dt = this.s.dt;
  496. var details = this.c.details;
  497. // The inline type always uses the first child as the target
  498. if ( details.type === 'inline' ) {
  499. details.target = 'td:first-child, th:first-child';
  500. }
  501. // Keyboard accessibility
  502. dt.on( 'draw.dtr', function () {
  503. that._tabIndexes();
  504. } );
  505. that._tabIndexes(); // Initial draw has already happened
  506. $( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
  507. if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
  508. $(this).click();
  509. }
  510. } );
  511. // type.target can be a string jQuery selector or a column index
  512. var target = details.target;
  513. var selector = typeof target === 'string' ? target : 'td, th';
  514. // Click handler to show / hide the details rows when they are available
  515. $( dt.table().body() )
  516. .on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
  517. // If the table is not collapsed (i.e. there is no hidden columns)
  518. // then take no action
  519. if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
  520. return;
  521. }
  522. // Check that the row is actually a DataTable's controlled node
  523. if ( ! dt.row( $(this).closest('tr') ).length ) {
  524. return;
  525. }
  526. // For column index, we determine if we should act or not in the
  527. // handler - otherwise it is already okay
  528. if ( typeof target === 'number' ) {
  529. var targetIdx = target < 0 ?
  530. dt.columns().eq(0).length + target :
  531. target;
  532. if ( dt.cell( this ).index().column !== targetIdx ) {
  533. return;
  534. }
  535. }
  536. // $().closest() includes itself in its check
  537. var row = dt.row( $(this).closest('tr') );
  538. // Check event type to do an action
  539. if ( e.type === 'click' ) {
  540. // The renderer is given as a function so the caller can execute it
  541. // only when they need (i.e. if hiding there is no point is running
  542. // the renderer)
  543. that._detailsDisplay( row, false );
  544. }
  545. else if ( e.type === 'mousedown' ) {
  546. // For mouse users, prevent the focus ring from showing
  547. $(this).css('outline', 'none');
  548. }
  549. else if ( e.type === 'mouseup' ) {
  550. // And then re-allow at the end of the click
  551. $(this).blur().css('outline', '');
  552. }
  553. } );
  554. },
  555. /**
  556. * Get the details to pass to a renderer for a row
  557. * @param {int} rowIdx Row index
  558. * @private
  559. */
  560. _detailsObj: function ( rowIdx )
  561. {
  562. var that = this;
  563. var dt = this.s.dt;
  564. return $.map( this.s.columns, function( col, i ) {
  565. if ( col.never ) {
  566. return;
  567. }
  568. return {
  569. title: dt.settings()[0].aoColumns[ i ].sTitle,
  570. data: dt.cell( rowIdx, i ).render( that.c.orthogonal ),
  571. hidden: dt.column( i ).visible() && !that.s.current[ i ],
  572. columnIndex: i,
  573. rowIndex: rowIdx
  574. };
  575. } );
  576. },
  577. /**
  578. * Find a breakpoint object from a name
  579. *
  580. * @param {string} name Breakpoint name to find
  581. * @return {object} Breakpoint description object
  582. * @private
  583. */
  584. _find: function ( name )
  585. {
  586. var breakpoints = this.c.breakpoints;
  587. for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
  588. if ( breakpoints[i].name === name ) {
  589. return breakpoints[i];
  590. }
  591. }
  592. },
  593. /**
  594. * Re-create the contents of the child rows as the display has changed in
  595. * some way.
  596. *
  597. * @private
  598. */
  599. _redrawChildren: function ()
  600. {
  601. var that = this;
  602. var dt = this.s.dt;
  603. dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
  604. var row = dt.row( idx );
  605. that._detailsDisplay( dt.row( idx ), true );
  606. } );
  607. },
  608. /**
  609. * Alter the table display for a resized viewport. This involves first
  610. * determining what breakpoint the window currently is in, getting the
  611. * column visibilities to apply and then setting them.
  612. *
  613. * @private
  614. */
  615. _resize: function ()
  616. {
  617. var that = this;
  618. var dt = this.s.dt;
  619. var width = $(window).width();
  620. var breakpoints = this.c.breakpoints;
  621. var breakpoint = breakpoints[0].name;
  622. var columns = this.s.columns;
  623. var i, ien;
  624. var oldVis = this.s.current.slice();
  625. // Determine what breakpoint we are currently at
  626. for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
  627. if ( width <= breakpoints[i].width ) {
  628. breakpoint = breakpoints[i].name;
  629. break;
  630. }
  631. }
  632. // Show the columns for that break point
  633. var columnsVis = this._columnsVisiblity( breakpoint );
  634. this.s.current = columnsVis;
  635. // Set the class before the column visibility is changed so event
  636. // listeners know what the state is. Need to determine if there are
  637. // any columns that are not visible but can be shown
  638. var collapsedClass = false;
  639. for ( i=0, ien=columns.length ; i<ien ; i++ ) {
  640. if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control ) {
  641. collapsedClass = true;
  642. break;
  643. }
  644. }
  645. $( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );
  646. var changed = false;
  647. dt.columns().eq(0).each( function ( colIdx, i ) {
  648. if ( columnsVis[i] !== oldVis[i] ) {
  649. changed = true;
  650. that._setColumnVis( colIdx, columnsVis[i] );
  651. }
  652. } );
  653. if ( changed ) {
  654. this._redrawChildren();
  655. // Inform listeners of the change
  656. $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
  657. }
  658. },
  659. /**
  660. * Determine the width of each column in the table so the auto column hiding
  661. * has that information to work with. This method is never going to be 100%
  662. * perfect since column widths can change slightly per page, but without
  663. * seriously compromising performance this is quite effective.
  664. *
  665. * @private
  666. */
  667. _resizeAuto: function ()
  668. {
  669. var dt = this.s.dt;
  670. var columns = this.s.columns;
  671. // Are we allowed to do auto sizing?
  672. if ( ! this.c.auto ) {
  673. return;
  674. }
  675. // Are there any columns that actually need auto-sizing, or do they all
  676. // have classes defined
  677. if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
  678. return;
  679. }
  680. // Clone the table with the current data in it
  681. var tableWidth = dt.table().node().offsetWidth;
  682. var columnWidths = dt.columns;
  683. var clonedTable = dt.table().node().cloneNode( false );
  684. var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
  685. var clonedBody = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8
  686. // Header
  687. var headerCells = dt.columns()
  688. .header()
  689. .filter( function (idx) {
  690. return dt.column(idx).visible();
  691. } )
  692. .to$()
  693. .clone( false )
  694. .css( 'display', 'table-cell' );
  695. // Body rows - we don't need to take account of DataTables' column
  696. // visibility since we implement our own here (hence the `display` set)
  697. $(clonedBody)
  698. .append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
  699. .find( 'th, td' ).css( 'display', '' );
  700. // Footer
  701. var footer = dt.table().footer();
  702. if ( footer ) {
  703. var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
  704. var footerCells = dt.columns()
  705. .header()
  706. .filter( function (idx) {
  707. return dt.column(idx).visible();
  708. } )
  709. .to$()
  710. .clone( false )
  711. .css( 'display', 'table-cell' );
  712. $('<tr/>')
  713. .append( footerCells )
  714. .appendTo( clonedFooter );
  715. }
  716. $('<tr/>')
  717. .append( headerCells )
  718. .appendTo( clonedHeader );
  719. // In the inline case extra padding is applied to the first column to
  720. // give space for the show / hide icon. We need to use this in the
  721. // calculation
  722. if ( this.c.details.type === 'inline' ) {
  723. $(clonedTable).addClass( 'dtr-inline collapsed' );
  724. }
  725. var inserted = $('<div/>')
  726. .css( {
  727. width: 1,
  728. height: 1,
  729. overflow: 'hidden'
  730. } )
  731. .append( clonedTable );
  732. inserted.insertBefore( dt.table().node() );
  733. // The cloned header now contains the smallest that each column can be
  734. headerCells.each( function (i) {
  735. var idx = dt.column.index( 'fromVisible', i );
  736. columns[ idx ].minWidth = this.offsetWidth || 0;
  737. } );
  738. inserted.remove();
  739. },
  740. /**
  741. * Set a column's visibility.
  742. *
  743. * We don't use DataTables' column visibility controls in order to ensure
  744. * that column visibility can Responsive can no-exist. Since only IE8+ is
  745. * supported (and all evergreen browsers of course) the control of the
  746. * display attribute works well.
  747. *
  748. * @param {integer} col Column index
  749. * @param {boolean} showHide Show or hide (true or false)
  750. * @private
  751. */
  752. _setColumnVis: function ( col, showHide )
  753. {
  754. var dt = this.s.dt;
  755. var display = showHide ? '' : 'none'; // empty string will remove the attr
  756. $( dt.column( col ).header() ).css( 'display', display );
  757. $( dt.column( col ).footer() ).css( 'display', display );
  758. dt.column( col ).nodes().to$().css( 'display', display );
  759. },
  760. /**
  761. * Update the cell tab indexes for keyboard accessibility. This is called on
  762. * every table draw - that is potentially inefficient, but also the least
  763. * complex option given that column visibility can change on the fly. Its a
  764. * shame user-focus was removed from CSS 3 UI, as it would have solved this
  765. * issue with a single CSS statement.
  766. *
  767. * @private
  768. */
  769. _tabIndexes: function ()
  770. {
  771. var dt = this.s.dt;
  772. var cells = dt.cells( { page: 'current' } ).nodes().to$();
  773. var ctx = dt.settings()[0];
  774. var target = this.c.details.target;
  775. cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );
  776. var selector = typeof target === 'number' ?
  777. ':eq('+target+')' :
  778. target;
  779. $( selector, dt.rows( { page: 'current' } ).nodes() )
  780. .attr( 'tabIndex', ctx.iTabIndex )
  781. .data( 'dtr-keyboard', 1 );
  782. }
  783. } );
  784. /**
  785. * List of default breakpoints. Each item in the array is an object with two
  786. * properties:
  787. *
  788. * * `name` - the breakpoint name.
  789. * * `width` - the breakpoint width
  790. *
  791. * @name Responsive.breakpoints
  792. * @static
  793. */
  794. Responsive.breakpoints = [
  795. { name: 'desktop', width: Infinity },
  796. { name: 'tablet-l', width: 1024 },
  797. { name: 'tablet-p', width: 768 },
  798. { name: 'mobile-l', width: 480 },
  799. { name: 'mobile-p', width: 320 }
  800. ];
  801. /**
  802. * Display methods - functions which define how the hidden data should be shown
  803. * in the table.
  804. *
  805. * @namespace
  806. * @name Responsive.defaults
  807. * @static
  808. */
  809. Responsive.display = {
  810. childRow: function ( row, update, render ) {
  811. if ( update ) {
  812. if ( $(row.node()).hasClass('parent') ) {
  813. row.child( render(), 'child' ).show();
  814. return true;
  815. }
  816. }
  817. else {
  818. if ( ! row.child.isShown() ) {
  819. row.child( render(), 'child' ).show();
  820. $( row.node() ).addClass( 'parent' );
  821. return true;
  822. }
  823. else {
  824. row.child( false );
  825. $( row.node() ).removeClass( 'parent' );
  826. return false;
  827. }
  828. }
  829. },
  830. childRowImmediate: function ( row, update, render ) {
  831. if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
  832. // User interaction and the row is show, or nothing to show
  833. row.child( false );
  834. $( row.node() ).removeClass( 'parent' );
  835. return false;
  836. }
  837. else {
  838. // Display
  839. row.child( render(), 'child' ).show();
  840. $( row.node() ).addClass( 'parent' );
  841. return true;
  842. }
  843. },
  844. // This is a wrapper so the modal options for Bootstrap and jQuery UI can
  845. // have options passed into them. This specific one doesn't need to be a
  846. // function but it is for consistency in the `modal` name
  847. modal: function ( options ) {
  848. return function ( row, update, render ) {
  849. if ( ! update ) {
  850. // Show a modal
  851. var close = function () {
  852. modal.remove(); // will tidy events for us
  853. $(document).off( 'keypress.dtr' );
  854. };
  855. var modal = $('<div class="dtr-modal"/>')
  856. .append( $('<div class="dtr-modal-display"/>')
  857. .append( $('<div class="dtr-modal-content"/>')
  858. .append( render() )
  859. )
  860. .append( $('<div class="dtr-modal-close">&times;</div>' )
  861. .click( function () {
  862. close();
  863. } )
  864. )
  865. )
  866. .append( $('<div class="dtr-modal-background"/>')
  867. .click( function () {
  868. close();
  869. } )
  870. )
  871. .appendTo( 'body' );
  872. $(document).on( 'keyup.dtr', function (e) {
  873. if ( e.keyCode === 27 ) {
  874. e.stopPropagation();
  875. close();
  876. }
  877. } );
  878. }
  879. else {
  880. $('div.dtr-modal-content')
  881. .empty()
  882. .append( render() );
  883. }
  884. if ( options && options.header ) {
  885. $('div.dtr-modal-content').prepend(
  886. '<h2>'+options.header( row )+'</h2>'
  887. );
  888. }
  889. };
  890. }
  891. };
  892. /**
  893. * Responsive default settings for initialisation
  894. *
  895. * @namespace
  896. * @name Responsive.defaults
  897. * @static
  898. */
  899. Responsive.defaults = {
  900. /**
  901. * List of breakpoints for the instance. Note that this means that each
  902. * instance can have its own breakpoints. Additionally, the breakpoints
  903. * cannot be changed once an instance has been creased.
  904. *
  905. * @type {Array}
  906. * @default Takes the value of `Responsive.breakpoints`
  907. */
  908. breakpoints: Responsive.breakpoints,
  909. /**
  910. * Enable / disable auto hiding calculations. It can help to increase
  911. * performance slightly if you disable this option, but all columns would
  912. * need to have breakpoint classes assigned to them
  913. *
  914. * @type {Boolean}
  915. * @default `true`
  916. */
  917. auto: true,
  918. /**
  919. * Details control. If given as a string value, the `type` property of the
  920. * default object is set to that value, and the defaults used for the rest
  921. * of the object - this is for ease of implementation.
  922. *
  923. * The object consists of the following properties:
  924. *
  925. * * `display` - A function that is used to show and hide the hidden details
  926. * * `renderer` - function that is called for display of the child row data.
  927. * The default function will show the data from the hidden columns
  928. * * `target` - Used as the selector for what objects to attach the child
  929. * open / close to
  930. * * `type` - `false` to disable the details display, `inline` or `column`
  931. * for the two control types
  932. *
  933. * @type {Object|string}
  934. */
  935. details: {
  936. display: Responsive.display.childRow,
  937. renderer: function ( api, rowIdx, columns ) {
  938. var data = $.map( columns, function ( col, i ) {
  939. return col.hidden ?
  940. '<li data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
  941. '<span class="dtr-title">'+
  942. col.title+
  943. '</span> '+
  944. '<span class="dtr-data">'+
  945. col.data+
  946. '</span>'+
  947. '</li>' :
  948. '';
  949. } ).join('');
  950. return data ?
  951. $('<ul data-dtr-index="'+rowIdx+'"/>').append( data ) :
  952. false;
  953. },
  954. target: 0,
  955. type: 'inline'
  956. },
  957. /**
  958. * Orthogonal data request option. This is used to define the data type
  959. * requested when Responsive gets the data to show in the child row.
  960. *
  961. * @type {String}
  962. */
  963. orthogonal: 'display'
  964. };
  965. /*
  966. * API
  967. */
  968. var Api = $.fn.dataTable.Api;
  969. // Doesn't do anything - work around for a bug in DT... Not documented
  970. Api.register( 'responsive()', function () {
  971. return this;
  972. } );
  973. Api.register( 'responsive.index()', function ( li ) {
  974. li = $(li);
  975. return {
  976. column: li.data('dtr-index'),
  977. row: li.parent().data('dtr-index')
  978. };
  979. } );
  980. Api.register( 'responsive.rebuild()', function () {
  981. return this.iterator( 'table', function ( ctx ) {
  982. if ( ctx._responsive ) {
  983. ctx._responsive._classLogic();
  984. }
  985. } );
  986. } );
  987. Api.register( 'responsive.recalc()', function () {
  988. return this.iterator( 'table', function ( ctx ) {
  989. if ( ctx._responsive ) {
  990. ctx._responsive._resizeAuto();
  991. ctx._responsive._resize();
  992. }
  993. } );
  994. } );
  995. Api.register( 'responsive.hasHidden()', function () {
  996. var ctx = this.context[0];
  997. return ctx._responsive ?
  998. $.inArray( false, ctx._responsive.s.current ) !== -1 :
  999. false;
  1000. } );
  1001. /**
  1002. * Version information
  1003. *
  1004. * @name Responsive.version
  1005. * @static
  1006. */
  1007. Responsive.version = '2.0.2';
  1008. $.fn.dataTable.Responsive = Responsive;
  1009. $.fn.DataTable.Responsive = Responsive;
  1010. // Attach a listener to the document which listens for DataTables initialisation
  1011. // events so we can automatically initialise
  1012. $(document).on( 'preInit.dt.dtr', function (e, settings, json) {
  1013. if ( e.namespace !== 'dt' ) {
  1014. return;
  1015. }
  1016. if ( $(settings.nTable).hasClass( 'responsive' ) ||
  1017. $(settings.nTable).hasClass( 'dt-responsive' ) ||
  1018. settings.oInit.responsive ||
  1019. DataTable.defaults.responsive
  1020. ) {
  1021. var init = settings.oInit.responsive;
  1022. if ( init !== false ) {
  1023. new Responsive( settings, $.isPlainObject( init ) ? init : {} );
  1024. }
  1025. }
  1026. } );
  1027. return Responsive;
  1028. }));