Pull merge.
[yaffs-website] / web / core / misc / tableresponsive.es6.js
1 /**
2  * @file
3  * Responsive table functionality.
4  */
5
6 (function($, Drupal, window) {
7   /**
8    * The TableResponsive object optimizes table presentation for screen size.
9    *
10    * A responsive table hides columns at small screen sizes, leaving the most
11    * important columns visible to the end user. Users should not be prevented
12    * from accessing all columns, however. This class adds a toggle to a table
13    * with hidden columns that exposes the columns. Exposing the columns will
14    * likely break layouts, but it provides the user with a means to access
15    * data, which is a guiding principle of responsive design.
16    *
17    * @constructor Drupal.TableResponsive
18    *
19    * @param {HTMLElement} table
20    *   The table element to initialize the responsive table on.
21    */
22   function TableResponsive(table) {
23     this.table = table;
24     this.$table = $(table);
25     this.showText = Drupal.t('Show all columns');
26     this.hideText = Drupal.t('Hide lower priority columns');
27     // Store a reference to the header elements of the table so that the DOM is
28     // traversed only once to find them.
29     this.$headers = this.$table.find('th');
30     // Add a link before the table for users to show or hide weight columns.
31     this.$link = $(
32       '<button type="button" class="link tableresponsive-toggle"></button>',
33     )
34       .attr(
35         'title',
36         Drupal.t(
37           'Show table cells that were hidden to make the table fit within a small screen.',
38         ),
39       )
40       .on('click', $.proxy(this, 'eventhandlerToggleColumns'));
41
42     this.$table.before(
43       $('<div class="tableresponsive-toggle-columns"></div>').append(
44         this.$link,
45       ),
46     );
47
48     // Attach a resize handler to the window.
49     $(window)
50       .on(
51         'resize.tableresponsive',
52         $.proxy(this, 'eventhandlerEvaluateColumnVisibility'),
53       )
54       .trigger('resize.tableresponsive');
55   }
56
57   /**
58    * Attach the tableResponsive function to {@link Drupal.behaviors}.
59    *
60    * @type {Drupal~behavior}
61    *
62    * @prop {Drupal~behaviorAttach} attach
63    *   Attaches tableResponsive functionality.
64    */
65   Drupal.behaviors.tableResponsive = {
66     attach(context, settings) {
67       const $tables = $(context)
68         .find('table.responsive-enabled')
69         .once('tableresponsive');
70       if ($tables.length) {
71         const il = $tables.length;
72         for (let i = 0; i < il; i++) {
73           TableResponsive.tables.push(new TableResponsive($tables[i]));
74         }
75       }
76     },
77   };
78
79   /**
80    * Extend the TableResponsive function with a list of managed tables.
81    */
82   $.extend(
83     TableResponsive,
84     /** @lends Drupal.TableResponsive */ {
85       /**
86        * Store all created instances.
87        *
88        * @type {Array.<Drupal.TableResponsive>}
89        */
90       tables: [],
91     },
92   );
93
94   /**
95    * Associates an action link with the table that will show hidden columns.
96    *
97    * Columns are assumed to be hidden if their header has the class priority-low
98    * or priority-medium.
99    */
100   $.extend(
101     TableResponsive.prototype,
102     /** @lends Drupal.TableResponsive# */ {
103       /**
104        * @param {jQuery.Event} e
105        *   The event triggered.
106        */
107       eventhandlerEvaluateColumnVisibility(e) {
108         const pegged = parseInt(this.$link.data('pegged'), 10);
109         const hiddenLength = this.$headers.filter(
110           '.priority-medium:hidden, .priority-low:hidden',
111         ).length;
112         // If the table has hidden columns, associate an action link with the
113         // table to show the columns.
114         if (hiddenLength > 0) {
115           this.$link.show().text(this.showText);
116         }
117         // When the toggle is pegged, its presence is maintained because the user
118         // has interacted with it. This is necessary to keep the link visible if
119         // the user adjusts screen size and changes the visibility of columns.
120         if (!pegged && hiddenLength === 0) {
121           this.$link.hide().text(this.hideText);
122         }
123       },
124
125       /**
126        * Toggle the visibility of columns based on their priority.
127        *
128        * Columns are classed with either 'priority-low' or 'priority-medium'.
129        *
130        * @param {jQuery.Event} e
131        *   The event triggered.
132        */
133       eventhandlerToggleColumns(e) {
134         e.preventDefault();
135         const self = this;
136         const $hiddenHeaders = this.$headers.filter(
137           '.priority-medium:hidden, .priority-low:hidden',
138         );
139         this.$revealedCells = this.$revealedCells || $();
140         // Reveal hidden columns.
141         if ($hiddenHeaders.length > 0) {
142           $hiddenHeaders.each(function(index, element) {
143             const $header = $(this);
144             const position = $header.prevAll('th').length;
145             self.$table.find('tbody tr').each(function() {
146               const $cells = $(this)
147                 .find('td')
148                 .eq(position);
149               $cells.show();
150               // Keep track of the revealed cells, so they can be hidden later.
151               self.$revealedCells = $()
152                 .add(self.$revealedCells)
153                 .add($cells);
154             });
155             $header.show();
156             // Keep track of the revealed headers, so they can be hidden later.
157             self.$revealedCells = $()
158               .add(self.$revealedCells)
159               .add($header);
160           });
161           this.$link.text(this.hideText).data('pegged', 1);
162         }
163         // Hide revealed columns.
164         else {
165           this.$revealedCells.hide();
166           // Strip the 'display:none' declaration from the style attributes of
167           // the table cells that .hide() added.
168           this.$revealedCells.each(function(index, element) {
169             const $cell = $(this);
170             const properties = $cell.attr('style').split(';');
171             const newProps = [];
172             // The hide method adds display none to the element. The element
173             // should be returned to the same state it was in before the columns
174             // were revealed, so it is necessary to remove the display none value
175             // from the style attribute.
176             const match = /^display\s*:\s*none$/;
177             for (let i = 0; i < properties.length; i++) {
178               const prop = properties[i];
179               prop.trim();
180               // Find the display:none property and remove it.
181               const isDisplayNone = match.exec(prop);
182               if (isDisplayNone) {
183                 continue;
184               }
185               newProps.push(prop);
186             }
187             // Return the rest of the style attribute values to the element.
188             $cell.attr('style', newProps.join(';'));
189           });
190           this.$link.text(this.showText).data('pegged', 0);
191           // Refresh the toggle link.
192           $(window).trigger('resize.tableresponsive');
193         }
194       },
195     },
196   );
197
198   // Make the TableResponsive object available in the Drupal namespace.
199   Drupal.TableResponsive = TableResponsive;
200 })(jQuery, Drupal, window);