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