All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.bower_components.datatables.net-responsive.js.dataTables.responsive.js Maven / Gradle / Ivy

The newest version!
/*! Responsive 2.2.3
 * 2014-2018 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     Responsive
 * @description Responsive tables plug-in for DataTables
 * @version     2.2.3
 * @file        dataTables.responsive.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2014-2018 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.10')) {
            throw 'DataTables Responsive requires DataTables 1.10.10 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 () {
                    // Use a small debounce to allow multiple columns to be set together
                    if (that._timer) {
                        clearTimeout(that._timer);
                    }

                    that._timer = setTimeout(function () {
                        that._timer = null;

                        that._classLogic();
                        that._resizeAuto();
                        that._resize();

                        that._redrawChildren();
                    }, 100);
                });

                // 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 () {
                    that._resizeAuto();
                    that._resize();

                    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, i) {
                if (dt.column(i).visible() === false) {
                    return 'not-visible';
                }
                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 < ien; i++) {
                if (display[i] === true) {
                    requiredWidth += columns[i].minWidth;
                }
            }

            // Second pass, use up any remaining width for other columns. For
            // scrolling tables we need to subtract the width of the scrollbar. It
            // may not be requires which makes this sub-optimal, but it would
            // require another full redraw to make complete use of those extra few
            // pixels
            var scrolling = dt.settings()[0].oScroll;
            var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
            var widthAvailable = dt.table().container().offsetWidth - bar;
            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 shown
            for (i = 0, ien = display.length; i < ien; i++) {
                if (columns[i].control) {
                    usedWidth -= columns[i].minWidth;
                }
            }

            // Allow columns to be shown (counting by priority and then right to
            // left) until we run out of room
            var empty = false;
            for (i = 0, ien = order.length; i < ien; i++) {
                var colIdx = order[i].columnIdx;

                if (display[colIdx] === '-' && !columns[colIdx].control && columns[colIdx].minWidth) {
                    // Once we've found a column that won't fit we don't let any
                    // others display either, or columns might disappear in the
                    // middle of the table
                    if (empty || usedWidth - columns[colIdx].minWidth < 0) {
                        empty = true;
                        display[colIdx] = false;
                    }
                    else {
                        display[colIdx] = true;
                    }

                    usedWidth -= columns[colIdx].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 taken
            var showControl = false;

            for (i = 0, ien = columns.length; i < ien; i++) {
                if (!columns[i].control && !columns[i].never && display[i] === false) {
                    showControl = true;
                    break;
                }
            }

            for (i = 0, ien = columns.length; i < ien; i++) {
                if (columns[i].control) {
                    display[i] = showControl;
                }

                // Replace not visible string with false from the control column detection above
                if (display[i] === 'not-visible') {
                    display[i] = false;
                }
            }

            // Finally we need to make sure that there is at least one column that
            // is visible
            if ($.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 dt = this.s.dt;
            var columns = dt.columns().eq(0).map(function (i) {
                var column = this.column(i);
                var className = column.header().className;
                var priority = dt.settings()[0].aoColumns[i].responsivePriority;

                if (priority === undefined) {
                    var dataPriority = $(column.header()).data('priority');

                    priority = dataPriority !== undefined ?
                        dataPriority * 1 :
                        10000;
                }

                return {
                    className: className,
                    includeIn: [],
                    auto: false,
                    control: false,
                    never: className.match(/\bnever\b/) ? true : false,
                    priority: priority
                };
            });

            // Simply add a breakpoint to `includeIn` array, ensuring that there are
            // no duplicates
            var 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 smaller
                    size = 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 larger
                    size = 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
                    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
            // class
            columns.each(function (col, i) {
                var classNames = col.className.split(' ');
                var hasClass = false;

                // Split the class name up so multiple rules can be applied if needed
                for (var k = 0, ken = classNames.length; k < ken; k++) {
                    var className = $.trim(classNames[k]);

                    if (className === 'all') {
                        // Include in all
                        hasClass = true;
                        col.includeIn = $.map(breakpoints, function (a) {
                            return a.name;
                        });
                        return;
                    }
                    else if (className === 'none' || col.never) {
                        // Include in none (default) and no auto
                        hasClass = 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 control
                        hasClass = 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 fully
                                column(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 qualifier
                                column(i, breakpoint.name, match[1], match[2]);
                            }
                        }
                    });
                }

                // If there was no control class, then automatic sizing is used
                if (!hasClass) {
                    col.auto = true;
                }
            });

            this.s.columns = columns;
        },


        /**
         * Show the details for the child row
         *
         * @param  {DataTables.Api} row    API instance for the row
         * @param  {boolean}        update Update flag
         * @private
         */
        _detailsDisplay: function (row, update) {
            var that = this;
            var dt = this.s.dt;
            var details = this.c.details;

            if (details && details.type !== false) {
                var res = details.display(row, update, function () {
                    return details.renderer(
                        dt, row[0], that._detailsObj(row[0])
                    );
                });

                if (res === true || res === false) {
                    $(dt.table().node()).triggerHandler('responsive-display.dt', [dt, row, res, update]);
                }
            }
        },


        /**
         * 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 target
            if (details.type === 'inline') {
                details.target = 'td:first-child, th:first-child';
            }

            // Keyboard accessibility
            dt.on('draw.dtr', function () {
                that._tabIndexes();
            });
            that._tabIndexes(); // Initial draw has already happened

            $(dt.table().body()).on('keyup.dtr', 'td, th', function (e) {
                if (e.keyCode === 13 && $(this).data('dtr-keyboard')) {
                    $(this).click();
                }
            });

            // type.target can be a string jQuery selector or a column index
            var target = details.target;
            var selector = typeof target === 'string' ? target : 'td, th';

            // Click handler to show / hide the details rows when they are available
            $(dt.table().body())
                .on('click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
                    // If the table is not collapsed (i.e. there is no hidden columns)
                    // then take no action
                    if (!$(dt.table().node()).hasClass('collapsed')) {
                        return;
                    }

                    // Check that the row is actually a DataTable's controlled node
                    if ($.inArray($(this).closest('tr').get(0), dt.rows().nodes().toArray()) === -1) {
                        return;
                    }

                    // For column index, we determine if we should act or not in the
                    // handler - otherwise it is already okay
                    if (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 check
                    var row = dt.row($(this).closest('tr'));

                    // Check event type to do an action
                    if (e.type === 'click') {
                        // The renderer is given as a function so the caller can execute it
                        // only when they need (i.e. if hiding there is no point is running
                        // the renderer)
                        that._detailsDisplay(row, false);
                    }
                    else if (e.type === 'mousedown') {
                        // For mouse users, prevent the focus ring from showing
                        $(this).css('outline', 'none');
                    }
                    else if (e.type === 'mouseup') {
                        // And then re-allow at the end of the click
                        $(this).blur().css('outline', '');
                    }
                });
        },


        /**
         * Get the details to pass to a renderer for a row
         * @param  {int} rowIdx Row index
         * @private
         */
        _detailsObj: function (rowIdx) {
            var that = this;
            var dt = this.s.dt;

            return $.map(this.s.columns, function (col, i) {
                // Never and control columns should not be passed to the renderer
                if (col.never || col.control) {
                    return;
                }

                return {
                    title: dt.settings()[0].aoColumns[i].sTitle,
                    data: dt.cell(rowIdx, i).render(that.c.orthogonal),
                    hidden: dt.column(i).visible() && !that.s.current[i],
                    columnIndex: i,
                    rowIndex: rowIdx
                };
            });
        },


        /**
         * Find a breakpoint object from a name
         *
         * @param  {string} name Breakpoint name to find
         * @return {object}      Breakpoint description object
         * @private
         */
        _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];
                }
            }
        },


        /**
         * Re-create the contents of the child rows as the display has changed in
         * some way.
         *
         * @private
         */
        _redrawChildren: function () {
            var that = this;
            var dt = this.s.dt;

            dt.rows({page: 'current'}).iterator('row', function (settings, idx) {
                var row = dt.row(idx);

                that._detailsDisplay(dt.row(idx), true);
            });
        },


        /**
         * 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 that = this;
            var dt = this.s.dt;
            var width = $(window).width();
            var breakpoints = this.c.breakpoints;
            var breakpoint = breakpoints[0].name;
            var columns = this.s.columns;
            var i, ien;
            var oldVis = this.s.current.slice();

            // Determine what breakpoint we are currently at
            for (i = breakpoints.length - 1; i >= 0; i--) {
                if (width <= breakpoints[i].width) {
                    breakpoint = breakpoints[i].name;
                    break;
                }
            }

            // Show the columns for that break point
            var columnsVis = this._columnsVisiblity(breakpoint);
            this.s.current = columnsVis;

            // Set the class before the column visibility is changed so event
            // listeners know what the state is. Need to determine if there are
            // any columns that are not visible but can be shown
            var collapsedClass = false;
            for (i = 0, ien = columns.length; i < ien; i++) {
                if (columnsVis[i] === false && !columns[i].never && !columns[i].control && !dt.column(i).visible() === false) {
                    collapsedClass = true;
                    break;
                }
            }

            $(dt.table().node()).toggleClass('collapsed', collapsedClass);

            var changed = false;
            var visible = 0;

            dt.columns().eq(0).each(function (colIdx, i) {
                if (columnsVis[i] === true) {
                    visible++;
                }

                if (columnsVis[i] !== oldVis[i]) {
                    changed = true;
                    that._setColumnVis(colIdx, columnsVis[i]);
                }
            });

            if (changed) {
                this._redrawChildren();

                // Inform listeners of the change
                $(dt.table().node()).trigger('responsive-resize.dt', [dt, this.s.current]);

                // If no records, update the "No records" display element
                if (dt.page.info().recordsDisplay === 0) {
                    $('td', dt.table().body()).eq(0).attr('colspan', visible);
                }
            }
        },


        /**
         * 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 defined
            if ($.inArray(true, $.map(columns, function (c) {
                return c.auto;
            })) === -1) {
                return;
            }

            // Need to restore all children. They will be reinstated by a re-render
            if (!$.isEmptyObject(_childNodeStore)) {
                $.each(_childNodeStore, function (key) {
                    var idx = key.split('-');

                    _childNodesRestore(dt, idx[0] * 1, idx[1] * 1);
                });
            }

            // Clone the table with the current data in it
            var 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()).clone(false, false).empty().appendTo(clonedTable); // use jQuery because of IE8

            // Header
            var headerCells = dt.columns()
                .header()
                .filter(function (idx) {
                    return dt.column(idx).visible();
                })
                .to$()
                .clone(false)
                .css('display', 'table-cell')
                .css('min-width', 0);

            // Body rows - we don't need to take account of DataTables' column
            // visibility since we implement our own here (hence the `display` set)
            $(clonedBody)
                .append($(dt.rows({page: 'current'}).nodes()).clone(false))
                .find('th, td').css('display', '');

            // Footer
            var footer = dt.table().footer();
            if (footer) {
                var clonedFooter = $(footer.cloneNode(false)).appendTo(clonedTable);
                var footerCells = dt.columns()
                    .footer()
                    .filter(function (idx) {
                        return dt.column(idx).visible();
                    })
                    .to$()
                    .clone(false)
                    .css('display', 'table-cell');

                $('')
                    .append(footerCells)
                    .appendTo(clonedFooter);
            }

            $('')
                .append(headerCells)
                .appendTo(clonedHeader);

            // In the inline case extra padding is applied to the first column to
            // give space for the show / hide icon. We need to use this in the
            // calculation
            if (this.c.details.type === 'inline') {
                $(clonedTable).addClass('dtr-inline collapsed');
            }

            // It is unsafe to insert elements with the same name into the DOM
            // multiple times. For example, cloning and inserting a checked radio
            // clears the chcecked state of the original radio.
            $(clonedTable).find('[name]').removeAttr('name');

            // A position absolute table would take the table out of the flow of
            // our container element, bypassing the height and width (Scroller)
            $(clonedTable).css('position', 'relative')

            var inserted = $('
') .css({ width: 1, height: 1, overflow: 'hidden', clear: 'both' }) .append(clonedTable); inserted.insertBefore(dt.table().node()); // The cloned header now contains the smallest that each column can be headerCells.each(function (i) { var idx = dt.column.index('fromVisible', i); columns[idx].minWidth = this.offsetWidth || 0; }); inserted.remove(); }, /** * Set a column's visibility. * * We don't use DataTables' column visibility controls in order to ensure * that column visibility can Responsive can no-exist. Since only IE8+ is * supported (and all evergreen browsers of course) the control of the * display attribute works well. * * @param {integer} col Column index * @param {boolean} showHide Show or hide (true or false) * @private */ _setColumnVis: function (col, showHide) { var dt = this.s.dt; var display = showHide ? '' : 'none'; // empty string will remove the attr $(dt.column(col).header()).css('display', display); $(dt.column(col).footer()).css('display', display); dt.column(col).nodes().to$().css('display', display); // If the are child nodes stored, we might need to reinsert them if (!$.isEmptyObject(_childNodeStore)) { dt.cells(null, col).indexes().each(function (idx) { _childNodesRestore(dt, idx.row, idx.column); }); } }, /** * Update the cell tab indexes for keyboard accessibility. This is called on * every table draw - that is potentially inefficient, but also the least * complex option given that column visibility can change on the fly. Its a * shame user-focus was removed from CSS 3 UI, as it would have solved this * issue with a single CSS statement. * * @private */ _tabIndexes: function () { var dt = this.s.dt; var cells = dt.cells({page: 'current'}).nodes().to$(); var ctx = dt.settings()[0]; var target = this.c.details.target; cells.filter('[data-dtr-keyboard]').removeData('[data-dtr-keyboard]'); if (typeof target === 'number') { dt.cells(null, target, {page: 'current'}).nodes().to$() .attr('tabIndex', ctx.iTabIndex) .data('dtr-keyboard', 1); } else { // This is a bit of a hack - we need to limit the selected nodes to just // those of this table if (target === 'td:first-child, th:first-child') { target = '>td:first-child, >th:first-child'; } $(target, dt.rows({page: 'current'}).nodes()) .attr('tabIndex', ctx.iTabIndex) .data('dtr-keyboard', 1); } } }); /** * 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} ]; /** * Display methods - functions which define how the hidden data should be shown * in the table. * * @namespace * @name Responsive.defaults * @static */ Responsive.display = { childRow: function (row, update, render) { if (update) { if ($(row.node()).hasClass('parent')) { row.child(render(), 'child').show(); return true; } } else { if (!row.child.isShown()) { row.child(render(), 'child').show(); $(row.node()).addClass('parent'); return true; } else { row.child(false); $(row.node()).removeClass('parent'); return false; } } }, childRowImmediate: function (row, update, render) { if ((!update && row.child.isShown()) || !row.responsive.hasHidden()) { // User interaction and the row is show, or nothing to show row.child(false); $(row.node()).removeClass('parent'); return false; } else { // Display row.child(render(), 'child').show(); $(row.node()).addClass('parent'); return true; } }, // This is a wrapper so the modal options for Bootstrap and jQuery UI can // have options passed into them. This specific one doesn't need to be a // function but it is for consistency in the `modal` name modal: function (options) { return function (row, update, render) { if (!update) { // Show a modal var close = function () { modal.remove(); // will tidy events for us $(document).off('keypress.dtr'); }; var modal = $('
') .append($('
') .append($('
') .append(render()) ) .append($('
×
') .click(function () { close(); }) ) ) .append($('
') .click(function () { close(); }) ) .appendTo('body'); $(document).on('keyup.dtr', function (e) { if (e.keyCode === 27) { e.stopPropagation(); close(); } }); } else { $('div.dtr-modal-content') .empty() .append(render()); } if (options && options.header) { $('div.dtr-modal-content').prepend( '

' + options.header(row) + '

' ); } }; } }; var _childNodeStore = {}; function _childNodes(dt, row, col) { var name = row + '-' + col; if (_childNodeStore[name]) { return _childNodeStore[name]; } // https://jsperf.com/childnodes-array-slice-vs-loop var nodes = []; var children = dt.cell(row, col).node().childNodes; for (var i = 0, ien = children.length; i < ien; i++) { nodes.push(children[i]); } _childNodeStore[name] = nodes; return nodes; } function _childNodesRestore(dt, row, col) { var name = row + '-' + col; if (!_childNodeStore[name]) { return; } var node = dt.cell(row, col).node(); var store = _childNodeStore[name]; var parent = store[0].parentNode; var parentChildren = parent.childNodes; var a = []; for (var i = 0, ien = parentChildren.length; i < ien; i++) { a.push(parentChildren[i]); } for (var j = 0, jen = a.length; j < jen; j++) { node.appendChild(a[j]); } _childNodeStore[name] = undefined; } /** * Display methods - functions which define how the hidden data should be shown * in the table. * * @namespace * @name Responsive.defaults * @static */ Responsive.renderer = { listHiddenNodes: function () { return function (api, rowIdx, columns) { var ul = $('
    '); var found = false; var data = $.each(columns, function (i, col) { if (col.hidden) { $( '
  • ' + '' + col.title + ' ' + '
  • ' ) .append($('').append(_childNodes(api, col.rowIndex, col.columnIndex)))// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) ) .appendTo(ul); found = true; } }); return found ? ul : false; }; }, listHidden: function () { return function (api, rowIdx, columns) { var data = $.map(columns, function (col) { return col.hidden ? '
  • ' + '' + col.title + ' ' + '' + col.data + '' + '
  • ' : ''; }).join(''); return data ? $('
      ').append(data) : false; } }, tableAll: function (options) { options = $.extend({ tableClass: '' }, options); return function (api, rowIdx, columns) { var data = $.map(columns, function (col) { return '' + '' + col.title + ':' + ' ' + '' + col.data + '' + ''; }).join(''); return $('').append(data); } } }; /** * 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: * * * `display` - A function that is used to show and hide the hidden details * * `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: { display: Responsive.display.childRow, renderer: Responsive.renderer.listHidden(), target: 0, type: 'inline' }, /** * Orthogonal data request option. This is used to define the data type * requested when Responsive gets the data to show in the child row. * * @type {String} */ orthogonal: 'display' }; /* * API */ var Api = $.fn.dataTable.Api; // Doesn't do anything - work around for a bug in DT... Not documented Api.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(); } }); }); Api.register('responsive.hasHidden()', function () { var ctx = this.context[0]; return ctx._responsive ? $.inArray(false, ctx._responsive.s.current) !== -1 : false; }); Api.registerPlural('columns().responsiveHidden()', 'column().responsiveHidden()', function () { return this.iterator('column', function (settings, column) { return settings._responsive ? settings._responsive.s.current[column] : false; }, 1); }); /** * Version information * * @name Responsive.version * @static */ Responsive.version = '2.2.3'; $.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('preInit.dt.dtr', function (e, settings, json) { if (e.namespace !== 'dt') { return; } 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; }));