Blame | Letzte Änderung | Log anzeigen | RSS feed
/*! Responsive 1.0.4* 2014 SpryMedia Ltd - datatables.net/license*//*** @summary Responsive* @description Responsive tables plug-in for DataTables* @version 1.0.4* @file dataTables.responsive.js* @author SpryMedia Ltd (www.sprymedia.co.uk)* @contact www.sprymedia.co.uk/contact* @copyright Copyright 2014 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(window, document, undefined) {var factory = function( $, DataTable ) {"use strict";/*** 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.1+** @example* $('#example').DataTable( {* responsive: true* } );* } );*/var Responsive = function ( settings, opts ) {// Sanity check that we are using DataTables 1.10 or newerif ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.1' ) ) {throw 'DataTables Responsive requires DataTables 1.10.1 or newer';}this.s = {dt: new DataTable.Api( settings ),columns: []};// Check if responsive has already been initialised on this tableif ( this.s.dt.settings()[0].responsive ) {return;}// details is an object, but for simplicity the user can give it as a stringif ( opts && typeof opts.details === 'string' ) {opts.details = { type: opts.details };}this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );settings.responsive = this;this._constructor();};Responsive.prototype = {/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** Constructor*//*** Initialise the Responsive instance** @private*/_constructor: function (){var that = this;var dt = this.s.dt;dt.settings()[0]._responsive = this;// Use DataTables' private throttle function to avoid processor thrashing$(window).on( 'resize.dtr orientationchange.dtr', dt.settings()[0].oApi._fnThrottle( function () {that._resize();} ) );// Destroy event handlerdt.on( 'destroy.dtr', function () {$(window).off( 'resize.dtr orientationchange.dtr draw.dtr' );} );// Reorder the breakpoints array here in case they have been added out// of orderthis.c.breakpoints.sort( function (a, b) {return a.width < b.width ? 1 :a.width > b.width ? -1 : 0;} );// Determine which columns are already hidden, and should therefore// remain hidden. TODO - should this be done? See thread 22677//// this.s.alwaysHidden = dt.columns(':hidden').indexes();this._classLogic();this._resizeAuto();// First pass - draw the table for the current viewport sizethis._resize();// Details handlervar details = this.c.details;if ( details.type ) {that._detailsInit();this._detailsVis();dt.on( 'column-visibility.dtr', function () {that._detailsVis();} );// Redraw the details box on each draw. This is used until// DataTables implements a native `updated` event for rowsdt.on( 'draw.dtr', function () {dt.rows().iterator( 'row', function ( settings, idx ) {var row = dt.row( idx );if ( row.child.isShown() ) {var info = that.c.details.renderer( dt, idx );row.child( info, 'child' ).show();}} );} );$(dt.table().node()).addClass( 'dtr-'+details.type );}},/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** 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;// 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 functionvar 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 columnsvar requiredWidth = 0;for ( i=0, ien=display.length ; i<ien ; i++ ) {if ( display[i] === true ) {requiredWidth += columns[i].minWidth;}}// Second pass, use up any remaining width for other columnsvar widthAvailable = dt.table().container().offsetWidth;var usedWidth = widthAvailable - requiredWidth;// Control column needs to always be included. This makes it sub-// optimal in terms of using the available with, but to stop layout// thrashing or overflow. Also we need to account for the control column// width first so we know how much width is available for the other// columns, since the control column might not be the first one shownfor ( i=0, ien=display.length ; i<ien ; i++ ) {if ( columns[i].control ) {usedWidth -= columns[i].minWidth;}}// Allow columns to be shown (counting from the left) until we run out// of roomfor ( i=0, ien=display.length ; i<ien ; i++ ) {if ( display[i] === '-' && ! columns[i].control ) {display[i] = usedWidth - columns[i].minWidth < 0 ?false :true;usedWidth -= columns[i].minWidth;}}// Determine if the 'control' column should be shown (if there is one).// This is the case when there is a hidden column (that is not the// control column). The two loops look inefficient here, but they are// trivial and will fly through. We need to know the outcome from the// first , before the action in the second can be takenvar showControl = false;for ( i=0, ien=columns.length ; i<ien ; i++ ) {if ( ! columns[i].control && ! columns[i].never && ! display[i] ) {showControl = true;break;}}for ( i=0, ien=columns.length ; i<ien ; i++ ) {if ( columns[i].control ) {display[i] = showControl;}}// Finally we need to make sure that there is at least one column that// is visibleif ( $.inArray( true, display ) === -1 ) {display[0] = true;}return display;},/*** Create the internal `columns` array with information about the columns* for the table. This includes determining which breakpoints the column* will appear in, based upon class names in the column, which makes up the* vast majority of this method.** @private*/_classLogic: function (){var that = this;var calc = {};var breakpoints = this.c.breakpoints;var columns = this.s.dt.columns().eq(0).map( function (i) {var className = this.column(i).header().className;return {className: className,includeIn: [],auto: false,control: false,never: className.match(/\bnever\b/) ? true : false};} );// Simply add a breakpoint to `includeIn` array, ensuring that there are// no duplicatesvar add = function ( colIdx, name ) {var includeIn = columns[ colIdx ].includeIn;if ( $.inArray( name, includeIn ) === -1 ) {includeIn.push( name );}};var column = function ( colIdx, name, operator, matched ) {var size, i, ien;if ( ! operator ) {columns[ colIdx ].includeIn.push( name );}else if ( operator === 'max-' ) {// Add this breakpoint and all smallersize = that._find( name ).width;for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {if ( breakpoints[i].width <= size ) {add( colIdx, breakpoints[i].name );}}}else if ( operator === 'min-' ) {// Add this breakpoint and all largersize = that._find( name ).width;for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {if ( breakpoints[i].width >= size ) {add( colIdx, breakpoints[i].name );}}}else if ( operator === 'not-' ) {// Add all but this breakpoint (xxx need extra information)for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {if ( breakpoints[i].name.indexOf( matched ) === -1 ) {add( colIdx, breakpoints[i].name );}}}};// Loop over each column and determine if it has a responsive control// classcolumns.each( function ( col, i ) {var classNames = col.className.split(' ');var hasClass = false;// Split the class name up so multiple rules can be applied if neededfor ( var k=0, ken=classNames.length ; k<ken ; k++ ) {var className = $.trim( classNames[k] );if ( className === 'all' ) {// Include in allhasClass = true;col.includeIn = $.map( breakpoints, function (a) {return a.name;} );return;}else if ( className === 'none' || className === 'never' ) {// Include in none (default) and no autohasClass = true;return;}else if ( className === 'control' ) {// Special column that is only visible, when one of the other// columns is hidden. This is used for the details controlhasClass = true;col.control = true;return;}$.each( breakpoints, function ( j, breakpoint ) {// Does this column have a class that matches this breakpoint?var brokenPoint = breakpoint.name.split('-');var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );var match = className.match( re );if ( match ) {hasClass = true;if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {// Class name matches breakpoint name fullycolumn( i, breakpoint.name, match[1], match[2]+match[3] );}else if ( match[2] === brokenPoint[0] && ! match[3] ) {// Class name matched primary breakpoint name with no qualifiercolumn( i, breakpoint.name, match[1], match[2] );}}} );}// If there was no control class, then automatic sizing is usedif ( ! hasClass ) {col.auto = true;}} );this.s.columns = columns;},/*** Initialisation for the details handler** @private*/_detailsInit: function (){var that = this;var dt = this.s.dt;var details = this.c.details;// The inline type always uses the first child as the targetif ( details.type === 'inline' ) {details.target = 'td:first-child';}// type.target can be a string jQuery selector or a column indexvar target = details.target;var selector = typeof target === 'string' ? target : 'td';// Click handler to show / hide the details rows when they are available$( dt.table().body() ).on( 'click', selector, function (e) {// If the table is not collapsed (i.e. there is no hidden columns)// then take no actionif ( ! $(dt.table().node()).hasClass('collapsed' ) ) {return;}// Check that the row is actually a DataTable's controlled nodeif ( ! dt.row( $(this).closest('tr') ).length ) {return;}// For column index, we determine if we should act or not in the// handler - otherwise it is already okayif ( typeof target === 'number' ) {var targetIdx = target < 0 ?dt.columns().eq(0).length + target :target;if ( dt.cell( this ).index().column !== targetIdx ) {return;}}// $().closest() includes itself in its checkvar row = dt.row( $(this).closest('tr') );if ( row.child.isShown() ) {row.child( false );$( row.node() ).removeClass( 'parent' );}else {var info = that.c.details.renderer( dt, row[0] );row.child( info, 'child' ).show();$( row.node() ).addClass( 'parent' );}} );},/*** Update the child rows in the table whenever the column visibility changes** @private*/_detailsVis: function (){var that = this;var dt = this.s.dt;// Find how many columns are hiddenvar hiddenColumns = dt.columns().indexes().filter( function ( idx ) {var col = dt.column( idx );if ( col.visible() ) {return null;}// Only counts as hidden if it doesn't have the `never` classreturn $( col.header() ).hasClass( 'never' ) ? null : idx;} );var haveHidden = true;if ( hiddenColumns.length === 0 || ( hiddenColumns.length === 1 && this.s.columns[ hiddenColumns[0] ].control ) ) {haveHidden = false;}if ( haveHidden ) {// Got hidden columns$( dt.table().node() ).addClass('collapsed');// Show all existing child rowsdt.rows().eq(0).each( function (idx) {var row = dt.row( idx );if ( row.child() ) {var info = that.c.details.renderer( dt, row[0] );// The renderer can return false to have no child rowif ( info === false ) {row.child.hide();}else {row.child( info, 'child' ).show();}}} );}else {// No hidden columns$( dt.table().node() ).removeClass('collapsed');// Hide all existing child rowsdt.rows().eq(0).each( function (idx) {dt.row( idx ).child.hide();} );}},/*** Find a breakpoint object from a name* @param {string} name Breakpoint name to find* @return {object} Breakpoint description object*/_find: function ( name ){var breakpoints = this.c.breakpoints;for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {if ( breakpoints[i].name === name ) {return breakpoints[i];}}},/*** Alter the table display for a resized viewport. This involves first* determining what breakpoint the window currently is in, getting the* column visibilities to apply and then setting them.** @private*/_resize: function (){var dt = this.s.dt;var width = $(window).width();var breakpoints = this.c.breakpoints;var breakpoint = breakpoints[0].name;// Determine what breakpoint we are currently atfor ( var i=breakpoints.length-1 ; i>=0 ; i-- ) {if ( width <= breakpoints[i].width ) {breakpoint = breakpoints[i].name;break;}}// Show the columns for that break pointvar columns = this._columnsVisiblity( breakpoint );dt.columns().eq(0).each( function ( colIdx, i ) {dt.column( colIdx ).visible( columns[i] );} );},/*** Determine the width of each column in the table so the auto column hiding* has that information to work with. This method is never going to be 100%* perfect since column widths can change slightly per page, but without* seriously compromising performance this is quite effective.** @private*/_resizeAuto: function (){var dt = this.s.dt;var columns = this.s.columns;// Are we allowed to do auto sizing?if ( ! this.c.auto ) {return;}// Are there any columns that actually need auto-sizing, or do they all// have classes definedif ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {return;}// Clone the table with the current data in itvar tableWidth = dt.table().node().offsetWidth;var columnWidths = dt.columns;var clonedTable = dt.table().node().cloneNode( false );var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );var clonedBody = $( dt.table().body().cloneNode( false ) ).appendTo( clonedTable );// This is a bit slow, but we need to get a clone of each row that// includes all columns. As such, try to do this as little as possible.dt.rows( { page: 'current' } ).indexes().flatten().each( function ( idx ) {var clone = dt.row( idx ).node().cloneNode( true );if ( dt.columns( ':hidden' ).flatten().length ) {$(clone).append( dt.cells( idx, ':hidden' ).nodes().to$().clone() );}$(clone).appendTo( clonedBody );} );var cells = dt.columns().header().to$().clone( false );$('<tr/>').append( cells ).appendTo( clonedHeader );var inserted = $('<div/>').css( {width: 1,height: 1,overflow: 'hidden'} ).append( clonedTable ).insertBefore( dt.table().node() );// The cloned header now contains the smallest that each column can bedt.columns().eq(0).each( function ( idx ) {columns[idx].minWidth = cells[ idx ].offsetWidth || 0;} );inserted.remove();}};/*** List of default breakpoints. Each item in the array is an object with two* properties:** * `name` - the breakpoint name.* * `width` - the breakpoint width** @name Responsive.breakpoints* @static*/Responsive.breakpoints = [{ name: 'desktop', width: Infinity },{ name: 'tablet-l', width: 1024 },{ name: 'tablet-p', width: 768 },{ name: 'mobile-l', width: 480 },{ name: 'mobile-p', width: 320 }];/*** Responsive default settings for initialisation** @namespace* @name Responsive.defaults* @static*/Responsive.defaults = {/*** List of breakpoints for the instance. Note that this means that each* instance can have its own breakpoints. Additionally, the breakpoints* cannot be changed once an instance has been creased.** @type {Array}* @default Takes the value of `Responsive.breakpoints`*/breakpoints: Responsive.breakpoints,/*** Enable / disable auto hiding calculations. It can help to increase* performance slightly if you disable this option, but all columns would* need to have breakpoint classes assigned to them** @type {Boolean}* @default `true`*/auto: true,/*** Details control. If given as a string value, the `type` property of the* default object is set to that value, and the defaults used for the rest* of the object - this is for ease of implementation.** The object consists of the following properties:** * `renderer` - function that is called for display of the child row data.* The default function will show the data from the hidden columns* * `target` - Used as the selector for what objects to attach the child* open / close to* * `type` - `false` to disable the details display, `inline` or `column`* for the two control types** @type {Object|string}*/details: {renderer: function ( api, rowIdx ) {var data = api.cells( rowIdx, ':hidden' ).eq(0).map( function ( cell ) {var header = $( api.column( cell.column ).header() );var idx = api.cell( cell ).index();if ( header.hasClass( 'control' ) || header.hasClass( 'never' ) ) {return '';}// Use a non-public DT API method to render the data for display// This needs to be updated when DT adds a suitable method for// this type of data retrievalvar dtPrivate = api.settings()[0];var cellData = dtPrivate.oApi._fnGetCellData(dtPrivate, idx.row, idx.column, 'display');var title = header.text();if ( title ) {title = title + ':';}return '<li data-dtr-index="'+idx.column+'">'+'<span class="dtr-title">'+title+'</span> '+'<span class="dtr-data">'+cellData+'</span>'+'</li>';} ).toArray().join('');return data ?$('<ul data-dtr-index="'+rowIdx+'"/>').append( data ) :false;},target: 0,type: 'inline'}};/** API*/var Api = $.fn.dataTable.Api;// Doesn't do anything - work around for a bug in DT... Not documentedApi.register( 'responsive()', function () {return this;} );Api.register( 'responsive.index()', function ( li ) {li = $(li);return {column: li.data('dtr-index'),row: li.parent().data('dtr-index')};} );Api.register( 'responsive.rebuild()', function () {return this.iterator( 'table', function ( ctx ) {if ( ctx._responsive ) {ctx._responsive._classLogic();}} );} );Api.register( 'responsive.recalc()', function () {return this.iterator( 'table', function ( ctx ) {if ( ctx._responsive ) {ctx._responsive._resizeAuto();ctx._responsive._resize();}} );} );/*** Version information** @name Responsive.version* @static*/Responsive.version = '1.0.4';$.fn.dataTable.Responsive = Responsive;$.fn.DataTable.Responsive = Responsive;// Attach a listener to the document which listens for DataTables initialisation// events so we can automatically initialise$(document).on( 'init.dt.dtr', function (e, settings, json) {if ( $(settings.nTable).hasClass( 'responsive' ) ||$(settings.nTable).hasClass( 'dt-responsive' ) ||settings.oInit.responsive ||DataTable.defaults.responsive) {var init = settings.oInit.responsive;if ( init !== false ) {new Responsive( settings, $.isPlainObject( init ) ? init : {} );}}} );return Responsive;}; // /factory// Define as an AMD module if possibleif ( typeof define === 'function' && define.amd ) {define( ['jquery', 'datatables'], factory );}else if ( typeof exports === 'object' ) {// Node/CommonJSfactory( require('jquery'), require('datatables') );}else if ( jQuery && !jQuery.fn.dataTable.Responsive ) {// Otherwise simply initialise as normal, stopping multiple evaluationfactory( jQuery, jQuery.fn.dataTable );}})(window, document);