475efa6945e8d7bf67c4e88a5569d347d6537018
[yaffs-website] / web / core / misc / tableselect.es6.js
1 /**
2  * @file
3  * Table select functionality.
4  */
5
6 (function($, Drupal) {
7   /**
8    * Initialize tableSelects.
9    *
10    * @type {Drupal~behavior}
11    *
12    * @prop {Drupal~behaviorAttach} attach
13    *   Attaches tableSelect functionality.
14    */
15   Drupal.behaviors.tableSelect = {
16     attach(context, settings) {
17       // Select the inner-most table in case of nested tables.
18       $(context)
19         .find('th.select-all')
20         .closest('table')
21         .once('table-select')
22         .each(Drupal.tableSelect);
23     },
24   };
25
26   /**
27    * Callback used in {@link Drupal.behaviors.tableSelect}.
28    */
29   Drupal.tableSelect = function() {
30     // Do not add a "Select all" checkbox if there are no rows with checkboxes
31     // in the table.
32     if ($(this).find('td input[type="checkbox"]').length === 0) {
33       return;
34     }
35
36     // Keep track of the table, which checkbox is checked and alias the
37     // settings.
38     const table = this;
39     let checkboxes;
40     let lastChecked;
41     const $table = $(table);
42     const strings = {
43       selectAll: Drupal.t('Select all rows in this table'),
44       selectNone: Drupal.t('Deselect all rows in this table'),
45     };
46     const updateSelectAll = function(state) {
47       // Update table's select-all checkbox (and sticky header's if available).
48       $table
49         .prev('table.sticky-header')
50         .addBack()
51         .find('th.select-all input[type="checkbox"]')
52         .each(function() {
53           const $checkbox = $(this);
54           const stateChanged = $checkbox.prop('checked') !== state;
55
56           $checkbox.attr(
57             'title',
58             state ? strings.selectNone : strings.selectAll,
59           );
60
61           /**
62            * @checkbox {HTMLElement}
63            */
64           if (stateChanged) {
65             $checkbox.prop('checked', state).trigger('change');
66           }
67         });
68     };
69
70     // Find all <th> with class select-all, and insert the check all checkbox.
71     $table
72       .find('th.select-all')
73       .prepend(
74         $('<input type="checkbox" class="form-checkbox" />').attr(
75           'title',
76           strings.selectAll,
77         ),
78       )
79       .on('click', event => {
80         if ($(event.target).is('input[type="checkbox"]')) {
81           // Loop through all checkboxes and set their state to the select all
82           // checkbox' state.
83           checkboxes.each(function() {
84             const $checkbox = $(this);
85             const stateChanged =
86               $checkbox.prop('checked') !== event.target.checked;
87
88             /**
89              * @checkbox {HTMLElement}
90              */
91             if (stateChanged) {
92               $checkbox.prop('checked', event.target.checked).trigger('change');
93             }
94             // Either add or remove the selected class based on the state of the
95             // check all checkbox.
96
97             /**
98              * @checkbox {HTMLElement}
99              */
100             $checkbox.closest('tr').toggleClass('selected', this.checked);
101           });
102           // Update the title and the state of the check all box.
103           updateSelectAll(event.target.checked);
104         }
105       });
106
107     // For each of the checkboxes within the table that are not disabled.
108     checkboxes = $table
109       .find('td input[type="checkbox"]:enabled')
110       .on('click', function(e) {
111         // Either add or remove the selected class based on the state of the
112         // check all checkbox.
113
114         /**
115          * @this {HTMLElement}
116          */
117         $(this)
118           .closest('tr')
119           .toggleClass('selected', this.checked);
120
121         // If this is a shift click, we need to highlight everything in the
122         // range. Also make sure that we are actually checking checkboxes
123         // over a range and that a checkbox has been checked or unchecked before.
124         if (e.shiftKey && lastChecked && lastChecked !== e.target) {
125           // We use the checkbox's parent <tr> to do our range searching.
126           Drupal.tableSelectRange(
127             $(e.target).closest('tr')[0],
128             $(lastChecked).closest('tr')[0],
129             e.target.checked,
130           );
131         }
132
133         // If all checkboxes are checked, make sure the select-all one is checked
134         // too, otherwise keep unchecked.
135         updateSelectAll(
136           checkboxes.length === checkboxes.filter(':checked').length,
137         );
138
139         // Keep track of the last checked checkbox.
140         lastChecked = e.target;
141       });
142
143     // If all checkboxes are checked on page load, make sure the select-all one
144     // is checked too, otherwise keep unchecked.
145     updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length);
146   };
147
148   /**
149    * @param {HTMLElement} from
150    *   The HTML element representing the "from" part of the range.
151    * @param {HTMLElement} to
152    *   The HTML element representing the "to" part of the range.
153    * @param {bool} state
154    *   The state to set on the range.
155    */
156   Drupal.tableSelectRange = function(from, to, state) {
157     // We determine the looping mode based on the order of from and to.
158     const mode =
159       from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
160
161     // Traverse through the sibling nodes.
162     for (let i = from[mode]; i; i = i[mode]) {
163       const $i = $(i);
164       // Make sure that we're only dealing with elements.
165       if (i.nodeType !== 1) {
166         continue;
167       }
168       // Either add or remove the selected class based on the state of the
169       // target checkbox.
170       $i.toggleClass('selected', state);
171       $i.find('input[type="checkbox"]').prop('checked', state);
172
173       if (to.nodeType) {
174         // If we are at the end of the range, stop.
175         if (i === to) {
176           break;
177         }
178       }
179       // A faster alternative to doing $(i).filter(to).length.
180       else if ($.filter(to, [i]).r.length) {
181         break;
182       }
183     }
184   };
185 })(jQuery, Drupal);