Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

dataTables.responsive.js 52KB

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