dataTables.responsive.js 32 KB

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