').append(cells).appendTo(clonedHeader);
});
// Always need an empty row that we can read widths from
var emptyRow = $('
').appendTo(clonedBody);
for (var i = 0; i < visibleColumns.count(); i++) {
emptyRow.append('
');
}
// Body rows
if (this.c.details.renderer._responsiveMovesNodes) {
// Slow but it allows for moving elements around the document
dt.rows({ page: 'current' }).every(function (rowIdx) {
var node = this.node();
if (! node) {
return;
}
// We clone the table's rows and cells to create the sizing table
var tr = node.cloneNode(false);
dt.cells(rowIdx, visibleColumns).every(function (rowIdx2, colIdx) {
// If nodes have been moved out (listHiddenNodes), we need to
// clone from the store
var store = that.s.childNodeStore[rowIdx + '-' + colIdx];
if (store) {
$(this.node().cloneNode(false))
.append($(store).clone())
.appendTo(tr);
}
else {
$(this.node()).clone(false).appendTo(tr);
}
});
clonedBody.append(tr);
});
}
else {
// This is much faster, but it doesn't account for moving nodes around
$(clonedBody)
.append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
.find( 'th, td' ).css( 'display', '' );
}
// Any cells which were hidden by Responsive in the host table, need to
// be visible here for the calculations
clonedBody.find('th, td').css('display', '');
// Footer
dt.table()
.footer.structure(visibleColumns)
.forEach((row) => {
var cells = row
.filter(function (el) {
return el ? true : false;
})
.map(function (el) {
return $(el.cell)
.clone(false)
.css('display', 'table-cell')
.css('width', 'auto')
.css('min-width', 0);
});
$('
').append(cells).appendTo(clonedFooter);
});
// 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 checked 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 table now contains the smallest that each column can be
emptyRow.children().each(function (i) {
var idx = dt.column.index('fromVisible', i);
columns[idx].minWidth = this.offsetWidth || 0;
});
inserted.remove();
},
/**
* Get the state of the current hidden columns - controlled by Responsive only
*/
_responsiveOnlyHidden: function () {
var dt = this.s.dt;
return $.map(this.s.current, function (v, i) {
// If the column is hidden by DataTables then it can't be hidden by
// Responsive!
if (dt.column(i).visible() === false) {
return true;
}
return v;
});
},
/**
* 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 that = this;
var dt = this.s.dt;
var display = showHide ? '' : 'none'; // empty string will remove the attr
this._setHeaderVis(col, showHide, dt.table().header.structure());
this._setHeaderVis(col, showHide, dt.table().footer.structure());
dt.column(col)
.nodes()
.to$()
.css('display', display)
.toggleClass('dtr-hidden', !showHide);
// We need to set a variable that DT can use when selecting visible
// columns without needing to query the DOM
dt.settings()[0].aoColumns[col].responsiveVisible = showHide;
// If the are child nodes stored, we might need to reinsert them
if (!$.isEmptyObject(this.s.childNodeStore)) {
dt.cells(null, col)
.indexes()
.each(function (idx) {
that._childNodesRestore(dt, idx.row, idx.column);
});
}
},
/**
* Set a column's visibility, taking into account multiple rows
* in a header / footer and colspan attributes
* @param {*} col
* @param {*} showHide
* @param {*} structure
*/
_setHeaderVis: function (col, showHide, structure) {
var that = this;
var display = showHide ? '' : 'none';
// We use the `null`s in the structure array to indicate that a cell
// should expand over that one if there is a colspan, but it might
// also have been filled by a rowspan, so we need to expand the
// rowspan cells down through the structure
structure.forEach(function (row, rowIdx) {
for (var col = 0; col < row.length; col++) {
if (row[col] && row[col].rowspan > 1) {
var span = row[col].rowspan;
for (var i=1 ; i= 0) {
if (row[search] && row[search].cell) {
row[search].cell.colSpan = that._colspan(row, search);
break;
}
search--;
}
}
});
},
/**
* How many columns should this cell span
*
* @param {*} row Header structure row
* @param {*} idx The column index of the cell to span
*/
_colspan: function (row, idx) {
var colspan = 1;
for (var col = idx + 1; col < row.length; col++) {
if (row[col] === null && this.s.current[col]) {
// colspan and not hidden by Responsive
colspan++;
}
else if (row[col]) {
// Got the next cell, jump out
break;
}
}
return colspan;
},
/**
* 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';
}
var rows = dt.rows({ page: 'current' }).nodes();
var nodes = target === 'tr'
? $(rows)
: $(target, rows);
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) {
var rowNode = $(row.node());
if (update) {
if (rowNode.hasClass('dtr-expanded')) {
row.child(render(), 'child').show();
return true;
}
}
else {
if (!rowNode.hasClass('dtr-expanded')) {
var rendered = render();
if (rendered === false) {
return false;
}
row.child(rendered, 'child').show();
return true;
}
else {
row.child(false);
return false;
}
}
},
childRowImmediate: function (row, update, render) {
var rowNode = $(row.node());
if (
(!update && rowNode.hasClass('dtr-expanded')) ||
!row.responsive.hasHidden()
) {
// User interaction and the row is show, or nothing to show
row.child(false);
return false;
}
else {
// Display
var rendered = render();
if (rendered === false) {
return false;
}
row.child(rendered, 'child').show();
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, closeCallback) {
var modal;
var rendered = render();
if (rendered === false) {
return false;
}
if (!update) {
// Show a modal
var close = function () {
modal.remove(); // will tidy events for us
$(document).off('keypress.dtr');
$(row.node()).removeClass('dtr-expanded');
closeCallback();
};
modal = $('')
.append(
$('')
.append(
$('')
.data('dtr-row-idx', row.index())
.append(rendered)
)
.append(
$(
'
×
'
).click(function () {
close();
})
)
)
.append(
$('').click(
function () {
close();
}
)
)
.appendTo('body');
$(row.node()).addClass('dtr-expanded');
$(document).on('keyup.dtr', function (e) {
if (e.keyCode === 27) {
e.stopPropagation();
close();
}
});
}
else {
modal = $('div.dtr-modal-content');
if (modal.length && row.index() === modal.data('dtr-row-idx')) {
modal.empty().append(rendered);
}
else {
// Modal not shown, nothing to update
return null;
}
}
if (options && options.header) {
$('div.dtr-modal-content').prepend(
'
' + options.header(row) + '
'
);
}
return true;
};
}
};
/**
* Display methods - functions which define how the hidden data should be shown
* in the table.
*
* @namespace
* @name Responsive.defaults
* @static
*/
Responsive.renderer = {
listHiddenNodes: function () {
var fn = function (api, rowIdx, columns) {
var that = this;
var ul = $(
'
'
);
var found = false;
$.each(columns, function (i, col) {
if (col.hidden) {
var klass = col.className
? 'class="' + col.className + '"'
: '';
$(
'
' +
'' +
col.title +
' ' +
'
'
)
.append(
$('').append(
that._childNodes(
api,
col.rowIndex,
col.columnIndex
)
)
) // api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
.appendTo(ul);
found = true;
}
});
return found ? ul : false;
};
fn._responsiveMovesNodes = true;
return fn;
},
listHidden: function () {
return function (api, rowIdx, columns) {
var data = $.map(columns, function (col) {
var klass = col.className
? 'class="' + col.className + '"'
: '';
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) {
var klass = col.className
? 'class="' + col.className + '"'
: '';
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 - workaround 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._responsiveOnlyHidden()) !== -1
: false;
});
Api.registerPlural(
'columns().responsiveHidden()',
'column().responsiveHidden()',
function () {
return this.iterator(
'column',
function (settings, column) {
return settings._responsive
? settings._responsive._responsiveOnlyHidden()[column]
: false;
},
1
);
}
);
/**
* Version information
*
* @name Responsive.version
* @static
*/
Responsive.version = '3.0.8';
$.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 DataTable;
}));