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