/*! Responsive 2.2.1-dev * 2014-2017 SpryMedia Ltd - datatables.net/license */ /** * @summary Responsive * @description Responsive tables plug-in for DataTables * @version 2.2.1-dev * @file dataTables.responsive.js * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact * @copyright Copyright 2014-2017 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details please refer to: http://www.datatables.net */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = function (root, $) { if ( ! root ) { root = window; } if ( ! $ || ! $.fn.dataTable ) { $ = require('datatables.net')(root, $).$; } return factory( $, root, root.document ); }; } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * Responsive is a plug-in for the DataTables library that makes use of * DataTables' ability to change the visibility of columns, changing the * visibility of columns so the displayed columns fit into the table container. * The end result is that complex tables will be dynamically adjusted to fit * into the viewport, be it on a desktop, tablet or mobile browser. * * Responsive for DataTables has two modes of operation, which can used * individually or combined: * * * Class name based control - columns assigned class names that match the * breakpoint logic can be shown / hidden as required for each breakpoint. * * Automatic control - columns are automatically hidden when there is no * room left to display them. Columns removed from the right. * * In additional to column visibility control, Responsive also has built into * options to use DataTables' child row display to show / hide the information * from the table that has been hidden. There are also two modes of operation * for this child row display: * * * Inline - when the control element that the user can use to show / hide * child rows is displayed inside the first column of the table. * * Column - where a whole column is dedicated to be the show / hide control. * * Initialisation of Responsive is performed by: * * * Adding the class `responsive` or `dt-responsive` to the table. In this case * Responsive will automatically be initialised with the default configuration * options when the DataTable is created. * * Using the `responsive` option in the DataTables configuration options. This * can also be used to specify the configuration options, or simply set to * `true` to use the defaults. * * @class * @param {object} settings DataTables settings object for the host table * @param {object} [opts] Configuration options * @requires jQuery 1.7+ * @requires DataTables 1.10.3+ * * @example * $('#example').DataTable( { * responsive: true * } ); * } ); */ var Responsive = function ( settings, opts ) { // Sanity check that we are using DataTables 1.10 or newer if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.3' ) ) { throw 'DataTables Responsive requires DataTables 1.10.3 or newer'; } this.s = { dt: new DataTable.Api( settings ), columns: [], current: [] }; // Check if responsive has already been initialised on this table if ( this.s.dt.settings()[0].responsive ) { return; } // details is an object, but for simplicity the user can give it as a string // or a boolean if ( opts && typeof opts.details === 'string' ) { opts.details = { type: opts.details }; } else if ( opts && opts.details === false ) { opts.details = { type: false }; } else if ( opts && opts.details === true ) { opts.details = { type: 'inline' }; } this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts ); settings.responsive = this; this._constructor(); }; $.extend( Responsive.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialise the Responsive instance * * @private */ _constructor: function () { var that = this; var dt = this.s.dt; var dtPrivateSettings = dt.settings()[0]; var oldWindowWidth = $(window).width(); dt.settings()[0]._responsive = this; // Use DataTables' throttle function to avoid processor thrashing on // resize $(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () { // iOS has a bug whereby resize can fire when only scrolling // See: http://stackoverflow.com/questions/8898412 var width = $(window).width(); if ( width !== oldWindowWidth ) { that._resize(); oldWindowWidth = width; } } ) ); // DataTables doesn't currently trigger an event when a row is added, so // we need to hook into its private API to enforce the hidden rows when // new data is added dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) { if ( $.inArray( false, that.s.current ) !== -1 ) { $('>td, >th', tr).each( function ( i ) { var idx = dt.column.index( 'toData', i ); if ( that.s.current[idx] === false ) { $(this).css('display', 'none'); } } ); } } ); // Destroy event handler dt.on( 'destroy.dtr', function () { dt.off( '.dtr' ); $( dt.table().body() ).off( '.dtr' ); $(window).off( 'resize.dtr orientationchange.dtr' ); // Restore the columns that we've hidden $.each( that.s.current, function ( i, val ) { if ( val === false ) { that._setColumnVis( i, true ); } } ); } ); // Reorder the breakpoints array here in case they have been added out // of order this.c.breakpoints.sort( function (a, b) { return a.width < b.width ? 1 : a.width > b.width ? -1 : 0; } ); this._classLogic(); this._resizeAuto(); // Details handler var details = this.c.details; if ( details.type !== false ) { that._detailsInit(); // DataTables will trigger this event on every column it shows and // hides individually dt.on( 'column-visibility.dtr', function (e, ctx, col, vis) { that._classLogic(); that._resizeAuto(); that._resize(); } ); // Redraw the details box on each draw which will happen if the data // has changed. This is used until DataTables implements a native // `updated` event for rows dt.on( 'draw.dtr', function () { that._redrawChildren(); } ); $(dt.table().node()).addClass( 'dtr-'+details.type ); } dt.on( 'column-reorder.dtr', function (e, settings, details) { that._classLogic(); that._resizeAuto(); that._resize(); } ); // Change in column sizes means we need to calc dt.on( 'column-sizing.dtr', function () { that._resizeAuto(); that._resize(); }); // On Ajax reload we want to reopen any child rows which are displayed // by responsive dt.on( 'preXhr.dtr', function () { var rowIds = []; dt.rows().every( function () { if ( this.child.isShown() ) { rowIds.push( this.id(true) ); } } ); dt.one( 'draw.dtr', function () { dt.rows( rowIds ).every( function () { that._detailsDisplay( this, false ); } ); } ); }); dt.on( 'init.dtr', function (e, settings, details) { that._resizeAuto(); that._resize(); // If columns were hidden, then DataTables needs to adjust the // column sizing if ( $.inArray( false, that.s.current ) ) { dt.columns.adjust(); } } ); // First pass - draw the table for the current viewport size this._resize(); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Calculate the visibility for the columns in a table for a given * breakpoint. The result is pre-determined based on the class logic if * class names are used to control all columns, but the width of the table * is also used if there are columns which are to be automatically shown * and hidden. * * @param {string} breakpoint Breakpoint name to use for the calculation * @return {array} Array of boolean values initiating the visibility of each * column. * @private */ _columnsVisiblity: function ( breakpoint ) { var dt = this.s.dt; var columns = this.s.columns; var i, ien; // Create an array that defines the column ordering based first on the // column's priority, and secondly the column index. This allows the // columns to be removed from the right if the priority matches var order = columns .map( function ( col, idx ) { return { columnIdx: idx, priority: col.priority }; } ) .sort( function ( a, b ) { if ( a.priority !== b.priority ) { return a.priority - b.priority; } return a.columnIdx - b.columnIdx; } ); // Class logic - determine which columns are in this breakpoint based // on the classes. If no class control (i.e. `auto`) then `-` is used // to indicate this to the rest of the function var display = $.map( columns, function ( col ) { return col.auto && col.minWidth === null ? false : col.auto === true ? '-' : $.inArray( breakpoint, col.includeIn ) !== -1; } ); // Auto column control - first pass: how much width is taken by the // ones that must be included from the non-auto columns var requiredWidth = 0; for ( i=0, ien=display.length ; i