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