dataTables.responsive.js 34 KB

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