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