/**
* @file
* Responsive table functionality.
*/
(function($, Drupal, window) {
/**
* The TableResponsive object optimizes table presentation for screen size.
*
* A responsive table hides columns at small screen sizes, leaving the most
* important columns visible to the end user. Users should not be prevented
* from accessing all columns, however. This class adds a toggle to a table
* with hidden columns that exposes the columns. Exposing the columns will
* likely break layouts, but it provides the user with a means to access
* data, which is a guiding principle of responsive design.
*
* @constructor Drupal.TableResponsive
*
* @param {HTMLElement} table
* The table element to initialize the responsive table on.
*/
function TableResponsive(table) {
this.table = table;
this.$table = $(table);
this.showText = Drupal.t('Show all columns');
this.hideText = Drupal.t('Hide lower priority columns');
// Store a reference to the header elements of the table so that the DOM is
// traversed only once to find them.
this.$headers = this.$table.find('th');
// Add a link before the table for users to show or hide weight columns.
this.$link = $(
'',
)
.attr(
'title',
Drupal.t(
'Show table cells that were hidden to make the table fit within a small screen.',
),
)
.on('click', $.proxy(this, 'eventhandlerToggleColumns'));
this.$table.before(
$('
').append(
this.$link,
),
);
// Attach a resize handler to the window.
$(window)
.on(
'resize.tableresponsive',
$.proxy(this, 'eventhandlerEvaluateColumnVisibility'),
)
.trigger('resize.tableresponsive');
}
/**
* Attach the tableResponsive function to {@link Drupal.behaviors}.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches tableResponsive functionality.
*/
Drupal.behaviors.tableResponsive = {
attach(context, settings) {
const $tables = $(context)
.find('table.responsive-enabled')
.once('tableresponsive');
if ($tables.length) {
const il = $tables.length;
for (let i = 0; i < il; i++) {
TableResponsive.tables.push(new TableResponsive($tables[i]));
}
}
},
};
/**
* Extend the TableResponsive function with a list of managed tables.
*/
$.extend(
TableResponsive,
/** @lends Drupal.TableResponsive */ {
/**
* Store all created instances.
*
* @type {Array.}
*/
tables: [],
},
);
/**
* Associates an action link with the table that will show hidden columns.
*
* Columns are assumed to be hidden if their header has the class priority-low
* or priority-medium.
*/
$.extend(
TableResponsive.prototype,
/** @lends Drupal.TableResponsive# */ {
/**
* @param {jQuery.Event} e
* The event triggered.
*/
eventhandlerEvaluateColumnVisibility(e) {
const pegged = parseInt(this.$link.data('pegged'), 10);
const hiddenLength = this.$headers.filter(
'.priority-medium:hidden, .priority-low:hidden',
).length;
// If the table has hidden columns, associate an action link with the
// table to show the columns.
if (hiddenLength > 0) {
this.$link.show().text(this.showText);
}
// When the toggle is pegged, its presence is maintained because the user
// has interacted with it. This is necessary to keep the link visible if
// the user adjusts screen size and changes the visibility of columns.
if (!pegged && hiddenLength === 0) {
this.$link.hide().text(this.hideText);
}
},
/**
* Toggle the visibility of columns based on their priority.
*
* Columns are classed with either 'priority-low' or 'priority-medium'.
*
* @param {jQuery.Event} e
* The event triggered.
*/
eventhandlerToggleColumns(e) {
e.preventDefault();
const self = this;
const $hiddenHeaders = this.$headers.filter(
'.priority-medium:hidden, .priority-low:hidden',
);
this.$revealedCells = this.$revealedCells || $();
// Reveal hidden columns.
if ($hiddenHeaders.length > 0) {
$hiddenHeaders.each(function(index, element) {
const $header = $(this);
const position = $header.prevAll('th').length;
self.$table.find('tbody tr').each(function() {
const $cells = $(this)
.find('td')
.eq(position);
$cells.show();
// Keep track of the revealed cells, so they can be hidden later.
self.$revealedCells = $()
.add(self.$revealedCells)
.add($cells);
});
$header.show();
// Keep track of the revealed headers, so they can be hidden later.
self.$revealedCells = $()
.add(self.$revealedCells)
.add($header);
});
this.$link.text(this.hideText).data('pegged', 1);
}
// Hide revealed columns.
else {
this.$revealedCells.hide();
// Strip the 'display:none' declaration from the style attributes of
// the table cells that .hide() added.
this.$revealedCells.each(function(index, element) {
const $cell = $(this);
const properties = $cell.attr('style').split(';');
const newProps = [];
// The hide method adds display none to the element. The element
// should be returned to the same state it was in before the columns
// were revealed, so it is necessary to remove the display none value
// from the style attribute.
const match = /^display\s*:\s*none$/;
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
prop.trim();
// Find the display:none property and remove it.
const isDisplayNone = match.exec(prop);
if (isDisplayNone) {
continue;
}
newProps.push(prop);
}
// Return the rest of the style attribute values to the element.
$cell.attr('style', newProps.join(';'));
});
this.$link.text(this.showText).data('pegged', 0);
// Refresh the toggle link.
$(window).trigger('resize.tableresponsive');
}
},
},
);
// Make the TableResponsive object available in the Drupal namespace.
Drupal.TableResponsive = TableResponsive;
})(jQuery, Drupal, window);