summaryrefslogtreecommitdiffstatshomepage
path: root/core/misc/tableresponsive.js
blob: 39ad39bf72fe49267e794caadb03aac8d42e06f8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
 * @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 = $(
      '<button type="button" class="link tableresponsive-toggle"></button>',
    )
      .attr(
        'title',
        Drupal.t(
          'Show table cells that were hidden to make the table fit within a small screen.',
        ),
      )
      .on('click', this.eventhandlerToggleColumns.bind(this));

    this.$table.before(
      $('<div class="tableresponsive-toggle-columns"></div>').append(
        this.$link,
      ),
    );

    // Attach a resize handler to the window.
    $(window)
      .on(
        'resize.tableresponsive',
        this.eventhandlerEvaluateColumnVisibility.bind(this),
      )
      .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) {
      once('tableresponsive', 'table.responsive-enabled', context).forEach(
        (table) => {
          TableResponsive.tables.push(new TableResponsive(table));
        },
      );
    },
  };

  /**
   * Extend the TableResponsive function with a list of managed tables.
   */
  $.extend(
    TableResponsive,
    /** @lends Drupal.TableResponsive */ {
      /**
       * Store all created instances.
       *
       * @type {Array.<Drupal.TableResponsive>}
       */
      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();
          this.$link[0].textContent = 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();
          this.$link[0].textContent = 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[0].textContent = this.hideText;
          this.$link.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[0].textContent = this.showText;
          this.$link.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);