dataTables.responsive.js 33 KB

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